feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies' Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk. Key changes include: - Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks. - Modified internal documentation and code comments to reflect PezkuwiChain naming and structure. - Replaced direct references to with or specific paths within the for XCM, Pezkuwi, and other modules. - Cleaned up deprecated issue and PR references in various and files, particularly in and modules. - Adjusted image and logo URLs in documentation to point to PezkuwiChain assets. - Removed or rephrased comments related to external Polkadot/Substrate PRs and issues. This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
[package]
|
||||
name = "pezpallet-nomination-pools"
|
||||
version = "25.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "FRAME nomination pools pallet"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
# parity
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
|
||||
# FRAME
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
log = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-staking = { workspace = true }
|
||||
|
||||
# Optional: use for testing and/or fuzzing
|
||||
pezpallet-balances = { optional = true, workspace = true }
|
||||
pezsp-tracing = { optional = true, workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezpallet-balances = { workspace = true, default-features = true }
|
||||
pezsp-tracing = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
fuzzing = ["pezpallet-balances", "pezsp-tracing"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"log/std",
|
||||
"pezpallet-balances?/std",
|
||||
"scale-info/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-staking/std",
|
||||
"pezsp-tracing?/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-balances?/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"pezsp-staking/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-balances?/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,82 @@
|
||||
[package]
|
||||
name = "pezpallet-nomination-pools-benchmarking"
|
||||
version = "26.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "FRAME nomination pools pallet benchmarking"
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
# parity
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
|
||||
# FRAME
|
||||
pezframe-benchmarking = { workspace = true }
|
||||
pezframe-election-provider-support = { workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezpallet-bags-list = { workspace = true }
|
||||
pezpallet-delegated-staking = { workspace = true }
|
||||
pezpallet-nomination-pools = { workspace = true }
|
||||
pezpallet-staking = { workspace = true }
|
||||
|
||||
# Bizinikiwi Primitives
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-runtime-interface = { workspace = true }
|
||||
pezsp-staking = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezpallet-balances = { workspace = true }
|
||||
pezpallet-staking-reward-curve = { workspace = true, default-features = true }
|
||||
pezpallet-timestamp = { workspace = true, default-features = true }
|
||||
pezsp-core = { workspace = true, default-features = true }
|
||||
pezsp-io = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"pezframe-benchmarking/std",
|
||||
"pezframe-election-provider-support/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"pezpallet-bags-list/std",
|
||||
"pezpallet-balances/std",
|
||||
"pezpallet-delegated-staking/std",
|
||||
"pezpallet-nomination-pools/std",
|
||||
"pezpallet-staking/std",
|
||||
"pezpallet-timestamp/std",
|
||||
"scale-info/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime-interface/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-staking/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-election-provider-support/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-bags-list/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezpallet-delegated-staking/runtime-benchmarks",
|
||||
"pezpallet-nomination-pools/runtime-benchmarks",
|
||||
"pezpallet-staking-reward-curve/runtime-benchmarks",
|
||||
"pezpallet-staking/runtime-benchmarks",
|
||||
"pezpallet-timestamp/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime-interface/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"pezsp-staking/runtime-benchmarks",
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,32 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Benchmarks for the nomination pools coupled with the staking and bags list pallets.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![recursion_limit = "256"]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub mod inner;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub use inner::*;
|
||||
|
||||
#[cfg(all(feature = "runtime-benchmarks", test))]
|
||||
pub(crate) mod mock;
|
||||
@@ -0,0 +1,190 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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::VoterBagsListInstance;
|
||||
use pezframe_election_provider_support::VoteWeight;
|
||||
use pezframe_support::{
|
||||
derive_impl,
|
||||
pezpallet_prelude::*,
|
||||
parameter_types,
|
||||
traits::{ConstU64, Nothing, VariantCountOf},
|
||||
PalletId,
|
||||
};
|
||||
use pezsp_runtime::{
|
||||
traits::{Convert, IdentityLookup},
|
||||
BuildStorage, FixedU128, Perbill,
|
||||
};
|
||||
|
||||
type AccountId = u128;
|
||||
type BlockNumber = u64;
|
||||
type Balance = u128;
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Runtime {
|
||||
type AccountId = AccountId;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Block = Block;
|
||||
type AccountData = pezpallet_balances::AccountData<Balance>;
|
||||
}
|
||||
|
||||
impl pezpallet_timestamp::Config for Runtime {
|
||||
type Moment = u64;
|
||||
type OnTimestampSet = ();
|
||||
type MinimumPeriod = ConstU64<5>;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: Balance = 10;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for Runtime {
|
||||
type Balance = Balance;
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
type FreezeIdentifier = RuntimeFreezeReason;
|
||||
type MaxFreezes = VariantCountOf<RuntimeFreezeReason>;
|
||||
type RuntimeHoldReason = RuntimeHoldReason;
|
||||
type RuntimeFreezeReason = RuntimeFreezeReason;
|
||||
}
|
||||
|
||||
pezpallet_staking_reward_curve::build! {
|
||||
const I_NPOS: pezsp_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 pezsp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS;
|
||||
}
|
||||
#[derive_impl(pezpallet_staking::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_staking::Config for Runtime {
|
||||
type OldCurrency = Balances;
|
||||
type Currency = Balances;
|
||||
type CurrencyBalance = Balance;
|
||||
type UnixTime = pezpallet_timestamp::Pallet<Self>;
|
||||
type AdminOrigin = pezframe_system::EnsureRoot<Self::AccountId>;
|
||||
type EraPayout = pezpallet_staking::ConvertCurve<RewardCurve>;
|
||||
type ElectionProvider =
|
||||
pezframe_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, (), ())>;
|
||||
type GenesisElectionProvider = Self::ElectionProvider;
|
||||
type VoterList = VoterList;
|
||||
type TargetList = pezpallet_staking::UseValidatorsMap<Self>;
|
||||
type EventListeners = (Pools, DelegatedStaking);
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static BagThresholds: &'static [VoteWeight] = &[10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000];
|
||||
}
|
||||
|
||||
impl pezpallet_bags_list::Config<VoterBagsListInstance> for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
type BagThresholds = BagThresholds;
|
||||
type ScoreProvider = Staking;
|
||||
type Score = VoteWeight;
|
||||
type MaxAutoRebagPerBlock = ();
|
||||
}
|
||||
|
||||
pub struct BalanceToU256;
|
||||
impl Convert<Balance, pezsp_core::U256> for BalanceToU256 {
|
||||
fn convert(n: Balance) -> pezsp_core::U256 {
|
||||
n.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct U256ToBalance;
|
||||
impl Convert<pezsp_core::U256, Balance> for U256ToBalance {
|
||||
fn convert(n: pezsp_core::U256) -> Balance {
|
||||
n.try_into().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static PostUnbondingPoolsWindow: u32 = 10;
|
||||
pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls");
|
||||
pub const MaxPointsToBalance: u8 = 10;
|
||||
}
|
||||
|
||||
impl pezpallet_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 =
|
||||
pezpallet_nomination_pools::adapter::DelegateStake<Self, Staking, DelegatedStaking>;
|
||||
type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow;
|
||||
type MaxMetadataLen = ConstU32<256>;
|
||||
type MaxUnbonding = ConstU32<8>;
|
||||
type PalletId = PoolsPalletId;
|
||||
type MaxPointsToBalance = MaxPointsToBalance;
|
||||
type AdminOrigin = pezframe_system::EnsureRoot<Self::AccountId>;
|
||||
type BlockNumberProvider = System;
|
||||
type Filter = Nothing;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const DelegatedStakingPalletId: PalletId = PalletId(*b"py/dlstk");
|
||||
pub const SlashRewardFraction: Perbill = Perbill::from_percent(1);
|
||||
}
|
||||
impl pezpallet_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 = pezframe_system::mocking::MockBlock<Runtime>;
|
||||
|
||||
pezframe_support::construct_runtime!(
|
||||
pub enum Runtime {
|
||||
System: pezframe_system,
|
||||
Timestamp: pezpallet_timestamp,
|
||||
Balances: pezpallet_balances,
|
||||
Staking: pezpallet_staking,
|
||||
VoterList: pezpallet_bags_list::<Instance1>,
|
||||
Pools: pezpallet_nomination_pools,
|
||||
DelegatedStaking: pezpallet_delegated_staking,
|
||||
}
|
||||
);
|
||||
|
||||
pub fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
let mut storage = pezframe_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
|
||||
let _ = pezpallet_nomination_pools::GenesisConfig::<Runtime> {
|
||||
min_join_bond: 2,
|
||||
min_create_bond: 2,
|
||||
max_pools: Some(3),
|
||||
max_members_per_pool: Some(3),
|
||||
max_members: Some(3 * 3),
|
||||
global_max_commission: Some(Perbill::from_percent(50)),
|
||||
}
|
||||
.assimilate_storage(&mut storage);
|
||||
pezsp_io::TestExternalities::from(storage)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
[package]
|
||||
name = "pezpallet-nomination-pools-fuzzer"
|
||||
version = "2.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Fuzzer for fixed point arithmetic primitives."
|
||||
documentation = "https://docs.rs/pezsp-arithmetic-fuzzer"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[[bin]]
|
||||
name = "call"
|
||||
path = "src/call.rs"
|
||||
|
||||
[dependencies]
|
||||
honggfuzz = { workspace = true }
|
||||
|
||||
pezpallet-nomination-pools = { features = [
|
||||
"fuzzing",
|
||||
], workspace = true, default-features = true }
|
||||
|
||||
pezframe-support = { workspace = true, default-features = true }
|
||||
pezframe-system = { workspace = true, default-features = true }
|
||||
|
||||
pezsp-io = { workspace = true, default-features = true }
|
||||
pezsp-runtime = { workspace = true, default-features = true }
|
||||
pezsp-tracing = { workspace = true, default-features = true }
|
||||
|
||||
log = { workspace = true, default-features = true }
|
||||
rand = { features = ["small_rng"], workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-nomination-pools/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,354 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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.
|
||||
|
||||
//! # Running
|
||||
//! Running this fuzzer can be done with `cargo hfuzz run call`. `honggfuzz` CLI
|
||||
//! options can be used by setting `HFUZZ_RUN_ARGS`, such as `-n 4` to use 4 threads.
|
||||
//!
|
||||
//! # Debugging a panic
|
||||
//! Once a panic is found, it can be debugged with
|
||||
//! `cargo hfuzz run-debug per_thing_rational hfuzz_workspace/call/*.fuzz`.
|
||||
|
||||
use pezframe_support::{
|
||||
assert_ok,
|
||||
traits::{Currency, GetCallName, UnfilteredDispatchable},
|
||||
};
|
||||
use honggfuzz::fuzz;
|
||||
use pezpallet_nomination_pools::{
|
||||
log,
|
||||
mock::*,
|
||||
pallet as pools,
|
||||
pallet::{BondedPools, Call as PoolsCall, Event as PoolsEvents, PoolMembers},
|
||||
BondExtra, BondedPool, GlobalMaxCommission, LastPoolId, MaxPoolMembers, MaxPoolMembersPerPool,
|
||||
MaxPools, MinCreateBond, MinJoinBond, PoolId,
|
||||
};
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
use pezsp_runtime::{assert_eq_error_rate, Perbill, Perquintill};
|
||||
|
||||
const ERA: BlockNumber = 1000;
|
||||
const MAX_ED_MULTIPLE: Balance = 10_000;
|
||||
const MIN_ED_MULTIPLE: Balance = 10;
|
||||
|
||||
// not quite elegant, just to make it available in random_signed_origin.
|
||||
const REWARD_AGENT_ACCOUNT: AccountId = 42;
|
||||
|
||||
/// Grab random accounts, either known ones, or new ones.
|
||||
fn random_signed_origin<R: Rng>(rng: &mut R) -> (RuntimeOrigin, AccountId) {
|
||||
let count = PoolMembers::<T>::count();
|
||||
if rng.gen::<bool>() && count > 0 {
|
||||
// take an existing account.
|
||||
let skip = rng.gen_range(0..count as usize);
|
||||
|
||||
// this is tricky: the account might be our reward agent, which we never want to be
|
||||
// randomly chosen here. Try another one, or, if it is only our agent, return a random
|
||||
// one nonetheless.
|
||||
let candidate = PoolMembers::<T>::iter_keys().skip(skip).take(1).next().unwrap();
|
||||
let acc =
|
||||
if candidate == REWARD_AGENT_ACCOUNT { rng.gen::<AccountId>() } else { candidate };
|
||||
|
||||
(RuntimeOrigin::signed(acc), acc)
|
||||
} else {
|
||||
// create a new account
|
||||
let acc = rng.gen::<AccountId>();
|
||||
(RuntimeOrigin::signed(acc), acc)
|
||||
}
|
||||
}
|
||||
|
||||
fn random_ed_multiple<R: Rng>(rng: &mut R) -> Balance {
|
||||
let multiple = rng.gen_range(MIN_ED_MULTIPLE..MAX_ED_MULTIPLE);
|
||||
ExistentialDeposit::get() * multiple
|
||||
}
|
||||
|
||||
fn fund_account<R: Rng>(rng: &mut R, account: &AccountId) {
|
||||
let target_amount = random_ed_multiple(rng);
|
||||
if let Some(top_up) = target_amount.checked_sub(Balances::free_balance(account)) {
|
||||
let _ = Balances::deposit_creating(account, top_up);
|
||||
}
|
||||
assert!(Balances::free_balance(account) >= target_amount);
|
||||
}
|
||||
|
||||
fn random_existing_pool<R: Rng>(mut rng: &mut R) -> Option<PoolId> {
|
||||
BondedPools::<T>::iter_keys().collect::<Vec<_>>().choose(&mut rng).map(|x| *x)
|
||||
}
|
||||
|
||||
fn random_call<R: Rng>(mut rng: &mut R) -> (pools::Call<T>, RuntimeOrigin) {
|
||||
let op = rng.gen::<usize>();
|
||||
let mut op_count = <pools::Call<T> as GetCallName>::get_call_names().len();
|
||||
// Exclude set_state, set_metadata, set_configs, update_roles and chill.
|
||||
op_count -= 5;
|
||||
|
||||
match op % op_count {
|
||||
0 => {
|
||||
// join
|
||||
let pool_id = random_existing_pool(&mut rng).unwrap_or_default();
|
||||
let (origin, who) = random_signed_origin(&mut rng);
|
||||
fund_account(&mut rng, &who);
|
||||
let amount = random_ed_multiple(&mut rng);
|
||||
(PoolsCall::<T>::join { amount, pool_id }, origin)
|
||||
},
|
||||
1 => {
|
||||
// bond_extra
|
||||
let (origin, who) = random_signed_origin(&mut rng);
|
||||
let extra = if rng.gen::<bool>() {
|
||||
BondExtra::Rewards
|
||||
} else {
|
||||
fund_account(&mut rng, &who);
|
||||
let amount = random_ed_multiple(&mut rng);
|
||||
BondExtra::FreeBalance(amount)
|
||||
};
|
||||
(PoolsCall::<T>::bond_extra { extra }, origin)
|
||||
},
|
||||
2 => {
|
||||
// claim_payout
|
||||
let (origin, _) = random_signed_origin(&mut rng);
|
||||
(PoolsCall::<T>::claim_payout {}, origin)
|
||||
},
|
||||
3 => {
|
||||
// unbond
|
||||
let (origin, who) = random_signed_origin(&mut rng);
|
||||
let amount = random_ed_multiple(&mut rng);
|
||||
(PoolsCall::<T>::unbond { member_account: who, unbonding_points: amount }, origin)
|
||||
},
|
||||
4 => {
|
||||
// pool_withdraw_unbonded
|
||||
let pool_id = random_existing_pool(&mut rng).unwrap_or_default();
|
||||
let (origin, _) = random_signed_origin(&mut rng);
|
||||
(PoolsCall::<T>::pool_withdraw_unbonded { pool_id, num_slashing_spans: 0 }, origin)
|
||||
},
|
||||
5 => {
|
||||
// withdraw_unbonded
|
||||
let (origin, who) = random_signed_origin(&mut rng);
|
||||
(
|
||||
PoolsCall::<T>::withdraw_unbonded { member_account: who, num_slashing_spans: 0 },
|
||||
origin,
|
||||
)
|
||||
},
|
||||
6 => {
|
||||
// create
|
||||
let (origin, who) = random_signed_origin(&mut rng);
|
||||
let amount = random_ed_multiple(&mut rng);
|
||||
fund_account(&mut rng, &who);
|
||||
let root = who;
|
||||
let bouncer = who;
|
||||
let nominator = who;
|
||||
(PoolsCall::<T>::create { amount, root, bouncer, nominator }, origin)
|
||||
},
|
||||
7 => {
|
||||
// nominate
|
||||
let (origin, _) = random_signed_origin(&mut rng);
|
||||
let pool_id = random_existing_pool(&mut rng).unwrap_or_default();
|
||||
let validators = Default::default();
|
||||
(PoolsCall::<T>::nominate { pool_id, validators }, origin)
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct RewardAgent {
|
||||
who: AccountId,
|
||||
pool_id: Option<PoolId>,
|
||||
expected_reward: Balance,
|
||||
}
|
||||
|
||||
// TODO: inject some slashes into the game.
|
||||
impl RewardAgent {
|
||||
fn new(who: AccountId) -> Self {
|
||||
Self { who, ..Default::default() }
|
||||
}
|
||||
|
||||
fn join(&mut self) {
|
||||
if self.pool_id.is_some() {
|
||||
return;
|
||||
}
|
||||
let pool_id = LastPoolId::<T>::get();
|
||||
let amount = 10 * ExistentialDeposit::get();
|
||||
let origin = RuntimeOrigin::signed(self.who);
|
||||
let _ = Balances::deposit_creating(&self.who, 10 * amount);
|
||||
self.pool_id = Some(pool_id);
|
||||
log::info!(target: "reward-agent", "🤖 reward agent joining in {} with {}", pool_id, amount);
|
||||
assert_ok!(PoolsCall::join::<T> { amount, pool_id }.dispatch_bypass_filter(origin));
|
||||
}
|
||||
|
||||
fn claim_payout(&mut self) {
|
||||
// 10 era later, we claim our payout. We expect our income to be roughly what we
|
||||
// calculated.
|
||||
if !PoolMembers::<T>::contains_key(&self.who) {
|
||||
log!(warn, "reward agent is not in the pool yet, cannot claim");
|
||||
return;
|
||||
}
|
||||
let pre = Balances::free_balance(&42);
|
||||
let origin = RuntimeOrigin::signed(42);
|
||||
assert_ok!(PoolsCall::<T>::claim_payout {}.dispatch_bypass_filter(origin));
|
||||
let post = Balances::free_balance(&42);
|
||||
|
||||
let income = post - pre;
|
||||
log::info!(
|
||||
target: "reward-agent", "🤖 CLAIM: actual: {}, expected: {}",
|
||||
income,
|
||||
self.expected_reward,
|
||||
);
|
||||
assert_eq_error_rate!(income, self.expected_reward, 10);
|
||||
self.expected_reward = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let mut reward_agent = RewardAgent::new(REWARD_AGENT_ACCOUNT);
|
||||
pezsp_tracing::try_init_simple();
|
||||
let mut ext = pezsp_io::TestExternalities::new_empty();
|
||||
let mut events_histogram = Vec::<(PoolsEvents<T>, u32)>::default();
|
||||
let mut iteration = 0 as BlockNumber;
|
||||
let mut ok = 0;
|
||||
let mut err = 0;
|
||||
|
||||
let dot: Balance = (10 as Balance).pow(10);
|
||||
ExistentialDeposit::set(dot);
|
||||
BondingDuration::set(8);
|
||||
|
||||
ext.execute_with(|| {
|
||||
MaxPoolMembers::<T>::set(Some(10_000));
|
||||
MaxPoolMembersPerPool::<T>::set(Some(1000));
|
||||
MaxPools::<T>::set(Some(1_000));
|
||||
GlobalMaxCommission::<T>::set(Some(Perbill::from_percent(25)));
|
||||
|
||||
MinCreateBond::<T>::set(10 * ExistentialDeposit::get());
|
||||
MinJoinBond::<T>::set(5 * ExistentialDeposit::get());
|
||||
System::set_block_number(1);
|
||||
});
|
||||
|
||||
loop {
|
||||
fuzz!(|seed: [u8; 32]| {
|
||||
use ::rand::{rngs::SmallRng, SeedableRng};
|
||||
let mut rng = SmallRng::from_seed(seed);
|
||||
|
||||
ext.execute_with(|| {
|
||||
let (call, origin) = random_call(&mut rng);
|
||||
let outcome = call.clone().dispatch_bypass_filter(origin.clone());
|
||||
iteration += 1;
|
||||
match outcome {
|
||||
Ok(_) => ok += 1,
|
||||
Err(_) => err += 1,
|
||||
};
|
||||
|
||||
log!(
|
||||
trace,
|
||||
"iteration {}, call {:?}, origin {:?}, outcome: {:?}, so far {} ok {} err",
|
||||
iteration,
|
||||
call,
|
||||
origin,
|
||||
outcome,
|
||||
ok,
|
||||
err,
|
||||
);
|
||||
|
||||
// possibly join the reward_agent
|
||||
if iteration > ERA / 2 && BondedPools::<T>::count() > 0 {
|
||||
reward_agent.join();
|
||||
}
|
||||
// and possibly roughly every 4 era, trigger payout for the agent. Doing this more
|
||||
// frequent is also harmless.
|
||||
if rng.gen_range(0..(4 * ERA)) == 0 {
|
||||
reward_agent.claim_payout();
|
||||
}
|
||||
|
||||
// execute sanity checks at a fixed interval, possibly on every block.
|
||||
if iteration %
|
||||
(std::env::var("SANITY_CHECK_INTERVAL")
|
||||
.ok()
|
||||
.and_then(|x| x.parse::<u64>().ok()))
|
||||
.unwrap_or(1) == 0
|
||||
{
|
||||
log!(info, "running sanity checks at {}", iteration);
|
||||
Pools::do_try_state(u8::MAX).unwrap();
|
||||
}
|
||||
|
||||
// collect and reset events.
|
||||
System::events()
|
||||
.into_iter()
|
||||
.map(|r| r.event)
|
||||
.filter_map(|e| {
|
||||
if let pezpallet_nomination_pools::mock::RuntimeEvent::Pools(inner) = e {
|
||||
Some(inner)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.for_each(|e| {
|
||||
if let Some((_, c)) = events_histogram
|
||||
.iter_mut()
|
||||
.find(|(x, _)| std::mem::discriminant(x) == std::mem::discriminant(&e))
|
||||
{
|
||||
*c += 1;
|
||||
} else {
|
||||
events_histogram.push((e, 1))
|
||||
}
|
||||
});
|
||||
System::reset_events();
|
||||
|
||||
// trigger an era change, and check the status of the reward agent.
|
||||
if iteration % ERA == 0 {
|
||||
CurrentEra::mutate(|c| *c += 1);
|
||||
BondedPools::<T>::iter().for_each(|(id, _)| {
|
||||
let amount = random_ed_multiple(&mut rng);
|
||||
let _ =
|
||||
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) {
|
||||
let all_points = BondedPool::<T>::get(id).map(|p| p.points).unwrap();
|
||||
let member_points =
|
||||
PoolMembers::<T>::get(reward_agent.who).map(|m| m.points).unwrap();
|
||||
let agent_share = Perquintill::from_rational(member_points, all_points);
|
||||
log::info!(
|
||||
target: "reward-agent",
|
||||
"🤖 REWARD = amount = {:?}, ratio: {:?}, share {:?}",
|
||||
amount,
|
||||
agent_share,
|
||||
agent_share * amount,
|
||||
);
|
||||
reward_agent.expected_reward += agent_share * amount;
|
||||
}
|
||||
});
|
||||
|
||||
log!(
|
||||
info,
|
||||
"iteration {}, {} pools, {} members, {} ok {} err, events = {:?}",
|
||||
iteration,
|
||||
BondedPools::<T>::count(),
|
||||
PoolMembers::<T>::count(),
|
||||
ok,
|
||||
err,
|
||||
events_histogram
|
||||
.iter()
|
||||
.map(|(x, c)| (
|
||||
format!("{:?}", x)
|
||||
.split(" ")
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.first()
|
||||
.cloned()
|
||||
.unwrap(),
|
||||
c,
|
||||
))
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "pezpallet-nomination-pools-runtime-api"
|
||||
version = "23.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Runtime API for nomination-pools FRAME pallet"
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
pezpallet-nomination-pools = { workspace = true }
|
||||
pezsp-api = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = ["codec/std", "pezpallet-nomination-pools/std", "pezsp-api/std"]
|
||||
runtime-benchmarks = [
|
||||
"pezpallet-nomination-pools/runtime-benchmarks",
|
||||
"pezsp-api/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
Runtime API definition for nomination-pools pallet.
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -0,0 +1,79 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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.
|
||||
|
||||
//! Runtime API definition for nomination-pools pallet.
|
||||
//! Currently supports only one rpc endpoint.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use codec::Codec;
|
||||
use pezpallet_nomination_pools::PoolId;
|
||||
|
||||
pezsp_api::decl_runtime_apis! {
|
||||
/// Runtime api for accessing information about nomination pools.
|
||||
pub trait NominationPoolsApi<AccountId, Balance>
|
||||
where
|
||||
AccountId: Codec,
|
||||
Balance: Codec,
|
||||
{
|
||||
/// Returns the pending rewards for the member that the AccountId was given for.
|
||||
fn pending_rewards(who: AccountId) -> Balance;
|
||||
|
||||
/// Returns the equivalent balance of `points` for a given pool.
|
||||
fn points_to_balance(pool_id: PoolId, points: Balance) -> Balance;
|
||||
|
||||
/// Returns the equivalent points of `new_funds` for a given pool.
|
||||
fn balance_to_points(pool_id: PoolId, new_funds: Balance) -> Balance;
|
||||
|
||||
/// Returns the pending slash for a given pool.
|
||||
fn pool_pending_slash(pool_id: PoolId) -> Balance;
|
||||
|
||||
/// Returns the pending slash for a given pool member.
|
||||
///
|
||||
/// If pending slash of the member exceeds `ExistentialDeposit`, it can be reported on
|
||||
/// chain.
|
||||
fn member_pending_slash(member: AccountId) -> Balance;
|
||||
|
||||
/// Returns true if the pool with `pool_id` needs migration.
|
||||
///
|
||||
/// This can happen when the `pezpallet-nomination-pools` has switched to using strategy
|
||||
/// [`DelegateStake`](pezpallet_nomination_pools::adapter::DelegateStake) but the pool
|
||||
/// still has funds that were staked using the older strategy
|
||||
/// [TransferStake](pezpallet_nomination_pools::adapter::TransferStake). Use
|
||||
/// [`migrate_pool_to_delegate_stake`](pezpallet_nomination_pools::Call::migrate_pool_to_delegate_stake)
|
||||
/// to migrate the pool.
|
||||
fn pool_needs_delegate_migration(pool_id: PoolId) -> bool;
|
||||
|
||||
/// Returns true if the delegated funds of the pool `member` needs migration.
|
||||
///
|
||||
/// Once a pool has successfully migrated to the strategy
|
||||
/// [`DelegateStake`](pezpallet_nomination_pools::adapter::DelegateStake), the funds of the
|
||||
/// member can be migrated from pool account to the member's account. Use
|
||||
/// [`migrate_delegation`](pezpallet_nomination_pools::Call::migrate_delegation)
|
||||
/// to migrate the funds of the pool member.
|
||||
fn member_needs_delegate_migration(member: AccountId) -> bool;
|
||||
|
||||
/// Returns the total contribution of a pool member including any balance that is unbonding.
|
||||
fn member_total_balance(who: AccountId) -> Balance;
|
||||
|
||||
/// Total balance contributed to the pool.
|
||||
fn pool_balance(pool_id: PoolId) -> Balance;
|
||||
|
||||
/// Returns the bonded account and reward account associated with the pool_id.
|
||||
fn pool_accounts(pool_id: PoolId) -> (AccountId, AccountId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,469 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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 pezframe_support::traits::tokens::{Fortitude::Polite, Preservation::Expendable};
|
||||
use pezsp_staking::{Agent, DelegationInterface, DelegationMigrator, Delegator};
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
/// A type that only belongs in context of a pool.
|
||||
///
|
||||
/// Maps directly [`Agent`] account.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Pool<T>(T);
|
||||
impl<AccountID> Into<Agent<AccountID>> for Pool<AccountID> {
|
||||
fn into(self) -> Agent<AccountID> {
|
||||
Agent::from(self.0)
|
||||
}
|
||||
}
|
||||
impl<T> From<T> for Pool<T> {
|
||||
fn from(acc: T) -> Self {
|
||||
Pool(acc)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Pool<T> {
|
||||
pub fn get(self) -> T {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that only belongs in context of a pool member.
|
||||
///
|
||||
/// Maps directly [`Delegator`] account.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Member<T>(T);
|
||||
impl<AccountID> Into<Delegator<AccountID>> for Member<AccountID> {
|
||||
fn into(self) -> Delegator<AccountID> {
|
||||
Delegator::from(self.0)
|
||||
}
|
||||
}
|
||||
impl<T> From<T> for Member<T> {
|
||||
fn from(acc: T) -> Self {
|
||||
Member(acc)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Member<T> {
|
||||
pub fn get(self) -> T {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// 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: pezframe_support::traits::tokens::Balance;
|
||||
type AccountId: Clone + core::fmt::Debug;
|
||||
type CoreStaking: StakingInterface<Balance = Self::Balance, AccountId = Self::AccountId>;
|
||||
|
||||
/// 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 can be withdrawn.
|
||||
fn transferable_balance(
|
||||
pool_account: Pool<Self::AccountId>,
|
||||
member_account: Member<Self::AccountId>,
|
||||
) -> Self::Balance;
|
||||
|
||||
/// Total balance of the pool including amount that is actively staked.
|
||||
fn total_balance(pool_account: Pool<Self::AccountId>) -> Option<Self::Balance>;
|
||||
|
||||
/// Amount of tokens delegated by the member.
|
||||
fn member_delegation_balance(member_account: Member<Self::AccountId>) -> Option<Self::Balance>;
|
||||
|
||||
/// See [`StakingInterface::active_stake`].
|
||||
fn active_stake(pool_account: Pool<Self::AccountId>) -> Self::Balance {
|
||||
Self::CoreStaking::active_stake(&pool_account.0).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// See [`StakingInterface::total_stake`].
|
||||
fn total_stake(pool_account: Pool<Self::AccountId>) -> Self::Balance {
|
||||
Self::CoreStaking::total_stake(&pool_account.0).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: Pool<Self::AccountId>) -> StakeStrategyType {
|
||||
match Self::CoreStaking::is_virtual_staker(&pool_account.0) {
|
||||
true => StakeStrategyType::Delegate,
|
||||
false => StakeStrategyType::Transfer,
|
||||
}
|
||||
}
|
||||
|
||||
/// See [`StakingInterface::nominate`].
|
||||
fn nominate(
|
||||
pool_account: Pool<Self::AccountId>,
|
||||
validators: Vec<Self::AccountId>,
|
||||
) -> DispatchResult {
|
||||
Self::CoreStaking::nominate(&pool_account.0, validators)
|
||||
}
|
||||
|
||||
/// See [`StakingInterface::chill`].
|
||||
fn chill(pool_account: Pool<Self::AccountId>) -> DispatchResult {
|
||||
Self::CoreStaking::chill(&pool_account.0)
|
||||
}
|
||||
|
||||
/// Pledge `amount` towards `pool_account` and update the pool bond. Also see
|
||||
/// [`StakingInterface::bond`].
|
||||
fn pledge_bond(
|
||||
who: Member<Self::AccountId>,
|
||||
pool_account: Pool<Self::AccountId>,
|
||||
reward_account: &Self::AccountId,
|
||||
amount: Self::Balance,
|
||||
bond_type: BondType,
|
||||
) -> DispatchResult;
|
||||
|
||||
/// See [`StakingInterface::unbond`].
|
||||
fn unbond(pool_account: Pool<Self::AccountId>, amount: Self::Balance) -> DispatchResult {
|
||||
Self::CoreStaking::unbond(&pool_account.0, amount)
|
||||
}
|
||||
|
||||
/// See [`StakingInterface::withdraw_unbonded`].
|
||||
fn withdraw_unbonded(
|
||||
pool_account: Pool<Self::AccountId>,
|
||||
num_slashing_spans: u32,
|
||||
) -> Result<bool, DispatchError> {
|
||||
Self::CoreStaking::withdraw_unbonded(pool_account.0, num_slashing_spans)
|
||||
}
|
||||
|
||||
/// Withdraw funds from pool account to member account.
|
||||
fn member_withdraw(
|
||||
who: Member<Self::AccountId>,
|
||||
pool_account: Pool<Self::AccountId>,
|
||||
amount: Self::Balance,
|
||||
num_slashing_spans: u32,
|
||||
) -> DispatchResult;
|
||||
|
||||
/// Dissolve the pool account.
|
||||
fn dissolve(pool_account: Pool<Self::AccountId>) -> DispatchResult;
|
||||
|
||||
/// Check if there is any pending slash for the pool.
|
||||
fn pending_slash(pool_account: Pool<Self::AccountId>) -> Self::Balance;
|
||||
|
||||
/// Slash the member account with `amount` against pending slashes for the pool.
|
||||
fn member_slash(
|
||||
who: Member<Self::AccountId>,
|
||||
pool_account: Pool<Self::AccountId>,
|
||||
amount: Self::Balance,
|
||||
maybe_reporter: Option<Self::AccountId>,
|
||||
) -> 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: Pool<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: Pool<Self::AccountId>,
|
||||
delegator: Member<Self::AccountId>,
|
||||
value: Self::Balance,
|
||||
) -> DispatchResult;
|
||||
|
||||
/// List of validators nominated by the pool account.
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn nominations(pool_account: Pool<Self::AccountId>) -> Option<Vec<Self::AccountId>> {
|
||||
Self::CoreStaking::nominations(&pool_account.0)
|
||||
}
|
||||
|
||||
/// 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: 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.
|
||||
#[deprecated = "consider migrating to DelegateStake"]
|
||||
pub struct TransferStake<T: Config, Staking: StakingInterface>(PhantomData<(T, Staking)>);
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T: Config, Staking: StakingInterface<Balance = BalanceOf<T>, AccountId = T::AccountId>>
|
||||
StakeStrategy for TransferStake<T, Staking>
|
||||
{
|
||||
type Balance = BalanceOf<T>;
|
||||
type AccountId = T::AccountId;
|
||||
type CoreStaking = Staking;
|
||||
|
||||
fn strategy_type() -> StakeStrategyType {
|
||||
StakeStrategyType::Transfer
|
||||
}
|
||||
|
||||
fn transferable_balance(
|
||||
pool_account: Pool<Self::AccountId>,
|
||||
_: Member<Self::AccountId>,
|
||||
) -> BalanceOf<T> {
|
||||
// free/liquid balance of the pool account.
|
||||
T::Currency::reducible_balance(&pool_account.get(), Expendable, Polite)
|
||||
}
|
||||
|
||||
fn total_balance(pool_account: Pool<Self::AccountId>) -> Option<BalanceOf<T>> {
|
||||
Some(T::Currency::total_balance(&pool_account.get()))
|
||||
}
|
||||
|
||||
fn member_delegation_balance(
|
||||
_member_account: Member<T::AccountId>,
|
||||
) -> Option<Staking::Balance> {
|
||||
// for transfer stake, no delegation exists.
|
||||
None
|
||||
}
|
||||
|
||||
fn pledge_bond(
|
||||
who: Member<T::AccountId>,
|
||||
pool_account: Pool<Self::AccountId>,
|
||||
reward_account: &Self::AccountId,
|
||||
amount: BalanceOf<T>,
|
||||
bond_type: BondType,
|
||||
) -> DispatchResult {
|
||||
match bond_type {
|
||||
BondType::Create => {
|
||||
// first bond
|
||||
T::Currency::transfer(&who.0, &pool_account.0, amount, Preservation::Expendable)?;
|
||||
Staking::bond(&pool_account.0, amount, &reward_account)
|
||||
},
|
||||
BondType::Extra => {
|
||||
// additional bond
|
||||
T::Currency::transfer(&who.0, &pool_account.0, amount, Preservation::Preserve)?;
|
||||
Staking::bond_extra(&pool_account.0, amount)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn member_withdraw(
|
||||
who: Member<Self::AccountId>,
|
||||
pool_account: Pool<Self::AccountId>,
|
||||
amount: BalanceOf<T>,
|
||||
_num_slashing_spans: u32,
|
||||
) -> DispatchResult {
|
||||
T::Currency::transfer(&pool_account.0, &who.0, amount, Preservation::Expendable)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dissolve(pool_account: Pool<Self::AccountId>) -> DispatchResult {
|
||||
defensive_assert!(
|
||||
T::Currency::total_balance(&pool_account.clone().get()).is_zero(),
|
||||
"dissolving pool should not have any balance"
|
||||
);
|
||||
|
||||
// Defensively force set balance to zero.
|
||||
T::Currency::set_balance(&pool_account.get(), Zero::zero());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pending_slash(_: Pool<Self::AccountId>) -> Self::Balance {
|
||||
// for transfer stake strategy, slashing is greedy and never deferred.
|
||||
Zero::zero()
|
||||
}
|
||||
|
||||
fn member_slash(
|
||||
_who: Member<Self::AccountId>,
|
||||
_pool: Pool<Self::AccountId>,
|
||||
_amount: Staking::Balance,
|
||||
_maybe_reporter: Option<T::AccountId>,
|
||||
) -> DispatchResult {
|
||||
Err(Error::<T>::Defensive(DefensiveError::DelegationUnsupported).into())
|
||||
}
|
||||
|
||||
fn migrate_nominator_to_agent(
|
||||
_pool: Pool<Self::AccountId>,
|
||||
_reward_account: &Self::AccountId,
|
||||
) -> DispatchResult {
|
||||
Err(Error::<T>::Defensive(DefensiveError::DelegationUnsupported).into())
|
||||
}
|
||||
|
||||
fn migrate_delegation(
|
||||
_pool: Pool<Self::AccountId>,
|
||||
_delegator: Member<Self::AccountId>,
|
||||
_value: Self::Balance,
|
||||
) -> DispatchResult {
|
||||
Err(Error::<T>::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<T: Config, Staking: StakingInterface, Delegation: DelegationInterface>(
|
||||
PhantomData<(T, Staking, Delegation)>,
|
||||
);
|
||||
|
||||
impl<
|
||||
T: Config,
|
||||
Staking: StakingInterface<Balance = BalanceOf<T>, AccountId = T::AccountId>,
|
||||
Delegation: DelegationInterface<Balance = BalanceOf<T>, AccountId = T::AccountId>
|
||||
+ DelegationMigrator<Balance = BalanceOf<T>, AccountId = T::AccountId>,
|
||||
> StakeStrategy for DelegateStake<T, Staking, Delegation>
|
||||
{
|
||||
type Balance = BalanceOf<T>;
|
||||
type AccountId = T::AccountId;
|
||||
type CoreStaking = Staking;
|
||||
|
||||
fn strategy_type() -> StakeStrategyType {
|
||||
StakeStrategyType::Delegate
|
||||
}
|
||||
|
||||
fn transferable_balance(
|
||||
pool_account: Pool<Self::AccountId>,
|
||||
member_account: Member<Self::AccountId>,
|
||||
) -> BalanceOf<T> {
|
||||
Delegation::agent_transferable_balance(pool_account.clone().into())
|
||||
// pool should always be an agent.
|
||||
.defensive_unwrap_or_default()
|
||||
.min(Delegation::delegator_balance(member_account.into()).unwrap_or_default())
|
||||
}
|
||||
|
||||
fn total_balance(pool_account: Pool<Self::AccountId>) -> Option<BalanceOf<T>> {
|
||||
Delegation::agent_balance(pool_account.into())
|
||||
}
|
||||
|
||||
fn member_delegation_balance(member_account: Member<T::AccountId>) -> Option<BalanceOf<T>> {
|
||||
Delegation::delegator_balance(member_account.into())
|
||||
}
|
||||
|
||||
fn pledge_bond(
|
||||
who: Member<T::AccountId>,
|
||||
pool_account: Pool<Self::AccountId>,
|
||||
reward_account: &Self::AccountId,
|
||||
amount: BalanceOf<T>,
|
||||
bond_type: BondType,
|
||||
) -> DispatchResult {
|
||||
match bond_type {
|
||||
BondType::Create => {
|
||||
// first delegation. Register agent first.
|
||||
Delegation::register_agent(pool_account.clone().into(), reward_account)?;
|
||||
Delegation::delegate(who.into(), pool_account.into(), amount)
|
||||
},
|
||||
BondType::Extra => {
|
||||
// additional delegation
|
||||
Delegation::delegate(who.into(), pool_account.into(), amount)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn member_withdraw(
|
||||
who: Member<Self::AccountId>,
|
||||
pool_account: Pool<Self::AccountId>,
|
||||
amount: BalanceOf<T>,
|
||||
num_slashing_spans: u32,
|
||||
) -> DispatchResult {
|
||||
Delegation::withdraw_delegation(who.into(), pool_account.into(), amount, num_slashing_spans)
|
||||
}
|
||||
|
||||
fn dissolve(pool_account: Pool<Self::AccountId>) -> DispatchResult {
|
||||
Delegation::remove_agent(pool_account.into())
|
||||
}
|
||||
|
||||
fn pending_slash(pool_account: Pool<Self::AccountId>) -> Self::Balance {
|
||||
Delegation::pending_slash(pool_account.into()).defensive_unwrap_or_default()
|
||||
}
|
||||
|
||||
fn member_slash(
|
||||
who: Member<Self::AccountId>,
|
||||
pool_account: Pool<Self::AccountId>,
|
||||
amount: BalanceOf<T>,
|
||||
maybe_reporter: Option<T::AccountId>,
|
||||
) -> DispatchResult {
|
||||
Delegation::delegator_slash(pool_account.into(), who.into(), amount, maybe_reporter)
|
||||
}
|
||||
|
||||
fn migrate_nominator_to_agent(
|
||||
pool: Pool<Self::AccountId>,
|
||||
reward_account: &Self::AccountId,
|
||||
) -> DispatchResult {
|
||||
Delegation::migrate_nominator_to_agent(pool.into(), reward_account)
|
||||
}
|
||||
|
||||
fn migrate_delegation(
|
||||
pool: Pool<Self::AccountId>,
|
||||
delegator: Member<Self::AccountId>,
|
||||
value: Self::Balance,
|
||||
) -> DispatchResult {
|
||||
Delegation::migrate_delegation(pool.into(), delegator.into(), value)
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn remove_as_agent(pool: Pool<Self::AccountId>) {
|
||||
Delegation::force_kill_agent(pool.into())
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,755 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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 super::*;
|
||||
use crate::{self as pools};
|
||||
use pezframe_support::{
|
||||
assert_ok, derive_impl, ord_parameter_types, parameter_types,
|
||||
traits::{fungible::Mutate, VariantCountOf},
|
||||
PalletId,
|
||||
};
|
||||
use pezframe_system::{EnsureSignedBy, RawOrigin};
|
||||
use pezsp_runtime::{BuildStorage, DispatchResult, FixedU128};
|
||||
use pezsp_staking::{
|
||||
Agent, DelegationInterface, DelegationMigrator, Delegator, OnStakingUpdate, Stake,
|
||||
};
|
||||
|
||||
pub type BlockNumber = u64;
|
||||
pub type AccountId = u128;
|
||||
pub type Balance = u128;
|
||||
pub type RewardCounter = FixedU128;
|
||||
// This sneaky little hack allows us to write code exactly as we would do in the pallet in the tests
|
||||
// as well, e.g. `StorageItem::<T>::get()`.
|
||||
pub type T = Runtime;
|
||||
pub type Currency = <T as Config>::Currency;
|
||||
|
||||
// Ext builder creates a pool with id 1.
|
||||
pub fn default_bonded_account() -> AccountId {
|
||||
Pools::generate_bonded_account(1)
|
||||
}
|
||||
|
||||
// Ext builder creates a pool with id 1.
|
||||
pub fn default_reward_account() -> AccountId {
|
||||
Pools::generate_reward_account(1)
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static MinJoinBondConfig: Balance = 2;
|
||||
pub static CurrentEra: EraIndex = 0;
|
||||
pub static BondingDuration: EraIndex = 3;
|
||||
pub storage BondedBalanceMap: BTreeMap<AccountId, Balance> = Default::default();
|
||||
// map from a user to a vec of eras and amounts being unlocked in each era.
|
||||
pub storage UnbondingBalanceMap: BTreeMap<AccountId, Vec<(EraIndex, Balance)>> = Default::default();
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub static MaxUnbonding: u32 = 8;
|
||||
pub static StakingMinBond: Balance = 10;
|
||||
pub storage Nominations: Option<Vec<AccountId>> = None;
|
||||
pub static RestrictedAccounts: Vec<AccountId> = Vec::new();
|
||||
}
|
||||
pub struct StakingMock;
|
||||
|
||||
impl StakingMock {
|
||||
pub(crate) fn set_bonded_balance(who: AccountId, bonded: Balance) {
|
||||
let mut x = BondedBalanceMap::get();
|
||||
x.insert(who, bonded);
|
||||
BondedBalanceMap::set(&x)
|
||||
}
|
||||
/// Mimics a slash towards a pool specified by `pool_id`.
|
||||
/// This reduces the bonded balance of a pool by `amount` and calls [`Pools::on_slash`] to
|
||||
/// enact changes in the nomination-pool pallet.
|
||||
///
|
||||
/// 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::generate_bonded_account(pool_id);
|
||||
let bonded = BondedBalanceMap::get();
|
||||
let pre_total = bonded.get(&acc).unwrap();
|
||||
Self::set_bonded_balance(acc, pre_total - amount);
|
||||
DelegateMock::on_slash(acc, amount);
|
||||
Pools::on_slash(&acc, pre_total - amount, &Default::default(), amount);
|
||||
}
|
||||
}
|
||||
|
||||
impl pezsp_staking::StakingInterface for StakingMock {
|
||||
type Balance = Balance;
|
||||
type AccountId = AccountId;
|
||||
type CurrencyToVote = ();
|
||||
|
||||
fn minimum_nominator_bond() -> Self::Balance {
|
||||
StakingMinBond::get()
|
||||
}
|
||||
fn minimum_validator_bond() -> Self::Balance {
|
||||
StakingMinBond::get()
|
||||
}
|
||||
|
||||
fn desired_validator_count() -> u32 {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
fn current_era() -> EraIndex {
|
||||
CurrentEra::get()
|
||||
}
|
||||
|
||||
fn bonding_duration() -> EraIndex {
|
||||
BondingDuration::get()
|
||||
}
|
||||
|
||||
fn status(
|
||||
_: &Self::AccountId,
|
||||
) -> Result<pezsp_staking::StakerStatus<Self::AccountId>, DispatchError> {
|
||||
Nominations::get()
|
||||
.map(|noms| pezsp_staking::StakerStatus::Nominator(noms))
|
||||
.ok_or(DispatchError::Other("NotStash"))
|
||||
}
|
||||
|
||||
fn is_virtual_staker(who: &Self::AccountId) -> bool {
|
||||
AgentBalanceMap::get().contains_key(who)
|
||||
}
|
||||
|
||||
fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult {
|
||||
let mut x = BondedBalanceMap::get();
|
||||
x.get_mut(who).map(|v| *v += extra);
|
||||
BondedBalanceMap::set(&x);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unbond(who: &Self::AccountId, amount: Self::Balance) -> DispatchResult {
|
||||
let mut x = BondedBalanceMap::get();
|
||||
*x.get_mut(who).unwrap() = x.get_mut(who).unwrap().saturating_sub(amount);
|
||||
BondedBalanceMap::set(&x);
|
||||
|
||||
let era = Self::current_era();
|
||||
let unlocking_at = era + Self::bonding_duration();
|
||||
let mut y = UnbondingBalanceMap::get();
|
||||
y.entry(*who).or_insert(Default::default()).push((unlocking_at, amount));
|
||||
UnbondingBalanceMap::set(&y);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_payee(_stash: &Self::AccountId, _reward_acc: &Self::AccountId) -> DispatchResult {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
fn chill(_: &Self::AccountId) -> pezsp_runtime::DispatchResult {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn withdraw_unbonded(who: Self::AccountId, _: u32) -> Result<bool, DispatchError> {
|
||||
let mut unbonding_map = UnbondingBalanceMap::get();
|
||||
|
||||
// closure to calculate the current unlocking funds across all eras/accounts.
|
||||
let unlocking = |pair: &Vec<(EraIndex, Balance)>| -> Balance {
|
||||
pair.iter()
|
||||
.try_fold(Zero::zero(), |acc: Balance, (_at, amount)| acc.checked_add(*amount))
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let staker_map = unbonding_map.get_mut(&who).ok_or("Nothing to unbond")?;
|
||||
let unlocking_before = unlocking(&staker_map);
|
||||
|
||||
let current_era = Self::current_era();
|
||||
|
||||
staker_map.retain(|(unlocking_at, _amount)| *unlocking_at > current_era);
|
||||
|
||||
// if there was a withdrawal, notify the pallet.
|
||||
let withdraw_amount = unlocking_before.saturating_sub(unlocking(&staker_map));
|
||||
Pools::on_withdraw(&who, withdraw_amount);
|
||||
DelegateMock::on_withdraw(who, withdraw_amount);
|
||||
|
||||
UnbondingBalanceMap::set(&unbonding_map);
|
||||
Ok(UnbondingBalanceMap::get().get(&who).unwrap().is_empty() &&
|
||||
BondedBalanceMap::get().get(&who).unwrap().is_zero())
|
||||
}
|
||||
|
||||
fn bond(stash: &Self::AccountId, value: Self::Balance, _: &Self::AccountId) -> DispatchResult {
|
||||
StakingMock::set_bonded_balance(*stash, value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn nominate(_: &Self::AccountId, nominations: Vec<Self::AccountId>) -> DispatchResult {
|
||||
Nominations::set(&Some(nominations));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn nominations(_: &Self::AccountId) -> Option<Vec<Self::AccountId>> {
|
||||
Nominations::get()
|
||||
}
|
||||
|
||||
fn stash_by_ctrl(_controller: &Self::AccountId) -> Result<Self::AccountId, DispatchError> {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
fn stake(who: &Self::AccountId) -> Result<Stake<Balance>, DispatchError> {
|
||||
match (UnbondingBalanceMap::get().get(who), BondedBalanceMap::get().get(who).copied()) {
|
||||
(None, None) => Err(DispatchError::Other("balance not found")),
|
||||
(Some(v), None) => Ok(Stake {
|
||||
total: v.into_iter().fold(0u128, |acc, &x| acc.saturating_add(x.1)),
|
||||
active: 0,
|
||||
}),
|
||||
(None, Some(v)) => Ok(Stake { total: v, active: v }),
|
||||
(Some(a), Some(b)) => Ok(Stake {
|
||||
total: a.into_iter().fold(0u128, |acc, &x| acc.saturating_add(x.1)) + b,
|
||||
active: b,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn election_ongoing() -> bool {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
fn force_unstake(_who: Self::AccountId) -> pezsp_runtime::DispatchResult {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
fn is_exposed_in_era(_who: &Self::AccountId, _era: &EraIndex) -> bool {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn add_era_stakers(
|
||||
_current_era: &EraIndex,
|
||||
_stash: &Self::AccountId,
|
||||
_exposures: Vec<(Self::AccountId, Self::Balance)>,
|
||||
) {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn set_current_era(_era: EraIndex) {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn max_exposure_page_size() -> pezsp_staking::Page {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
|
||||
fn slash_reward_fraction() -> Perbill {
|
||||
unimplemented!("method currently not used in testing")
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
// Map of agent to their (delegated balance, unclaimed withdrawal, pending slash).
|
||||
pub storage AgentBalanceMap: BTreeMap<AccountId, (Balance, Balance, Balance)> = Default::default();
|
||||
pub storage DelegatorBalanceMap: BTreeMap<AccountId, Balance> = Default::default();
|
||||
}
|
||||
pub struct DelegateMock;
|
||||
impl DelegationInterface for DelegateMock {
|
||||
type Balance = Balance;
|
||||
type AccountId = AccountId;
|
||||
fn agent_balance(agent: Agent<Self::AccountId>) -> Option<Self::Balance> {
|
||||
AgentBalanceMap::get()
|
||||
.get(&agent.get())
|
||||
.copied()
|
||||
.map(|(delegated, _, pending)| delegated - pending)
|
||||
}
|
||||
|
||||
fn agent_transferable_balance(agent: Agent<Self::AccountId>) -> Option<Self::Balance> {
|
||||
AgentBalanceMap::get()
|
||||
.get(&agent.get())
|
||||
.copied()
|
||||
.map(|(_, unclaimed_withdrawals, _)| unclaimed_withdrawals)
|
||||
}
|
||||
|
||||
fn delegator_balance(delegator: Delegator<Self::AccountId>) -> Option<Self::Balance> {
|
||||
DelegatorBalanceMap::get().get(&delegator.get()).copied()
|
||||
}
|
||||
|
||||
fn register_agent(
|
||||
agent: Agent<Self::AccountId>,
|
||||
_reward_account: &Self::AccountId,
|
||||
) -> DispatchResult {
|
||||
let mut agents = AgentBalanceMap::get();
|
||||
agents.insert(agent.get(), (0, 0, 0));
|
||||
AgentBalanceMap::set(&agents);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn remove_agent(agent: Agent<Self::AccountId>) -> DispatchResult {
|
||||
let mut agents = AgentBalanceMap::get();
|
||||
let agent = agent.get();
|
||||
assert!(agents.contains_key(&agent));
|
||||
agents.remove(&agent);
|
||||
AgentBalanceMap::set(&agents);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn delegate(
|
||||
delegator: Delegator<Self::AccountId>,
|
||||
agent: Agent<Self::AccountId>,
|
||||
amount: Self::Balance,
|
||||
) -> DispatchResult {
|
||||
let delegator = delegator.get();
|
||||
let mut delegators = DelegatorBalanceMap::get();
|
||||
delegators.entry(delegator).and_modify(|b| *b += amount).or_insert(amount);
|
||||
DelegatorBalanceMap::set(&delegators);
|
||||
|
||||
let agent = agent.get();
|
||||
let mut agents = AgentBalanceMap::get();
|
||||
agents
|
||||
.get_mut(&agent)
|
||||
.map(|(d, _, _)| *d += amount)
|
||||
.ok_or(DispatchError::Other("agent not registered"))?;
|
||||
AgentBalanceMap::set(&agents);
|
||||
|
||||
if BondedBalanceMap::get().contains_key(&agent) {
|
||||
StakingMock::bond_extra(&agent, amount)
|
||||
} else {
|
||||
// reward account does not matter in this context.
|
||||
StakingMock::bond(&agent, amount, &999)
|
||||
}
|
||||
}
|
||||
|
||||
fn withdraw_delegation(
|
||||
delegator: Delegator<Self::AccountId>,
|
||||
agent: Agent<Self::AccountId>,
|
||||
amount: Self::Balance,
|
||||
_num_slashing_spans: u32,
|
||||
) -> DispatchResult {
|
||||
let mut delegators = DelegatorBalanceMap::get();
|
||||
delegators.get_mut(&delegator.get()).map(|b| *b -= amount);
|
||||
DelegatorBalanceMap::set(&delegators);
|
||||
|
||||
let mut agents = AgentBalanceMap::get();
|
||||
agents.get_mut(&agent.get()).map(|(d, u, _)| {
|
||||
*d -= amount;
|
||||
*u -= amount;
|
||||
});
|
||||
AgentBalanceMap::set(&agents);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn pending_slash(agent: Agent<Self::AccountId>) -> Option<Self::Balance> {
|
||||
AgentBalanceMap::get()
|
||||
.get(&agent.get())
|
||||
.copied()
|
||||
.map(|(_, _, pending_slash)| pending_slash)
|
||||
}
|
||||
|
||||
fn delegator_slash(
|
||||
agent: Agent<Self::AccountId>,
|
||||
delegator: Delegator<Self::AccountId>,
|
||||
value: Self::Balance,
|
||||
_maybe_reporter: Option<Self::AccountId>,
|
||||
) -> DispatchResult {
|
||||
let mut delegators = DelegatorBalanceMap::get();
|
||||
delegators.get_mut(&delegator.get()).map(|b| *b -= value);
|
||||
DelegatorBalanceMap::set(&delegators);
|
||||
|
||||
let mut agents = AgentBalanceMap::get();
|
||||
agents.get_mut(&agent.get()).map(|(_, _, p)| {
|
||||
p.saturating_reduce(value);
|
||||
});
|
||||
AgentBalanceMap::set(&agents);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl DelegateMock {
|
||||
pub fn set_agent_balance(who: AccountId, delegated: Balance) {
|
||||
let mut agents = AgentBalanceMap::get();
|
||||
agents.insert(who, (delegated, 0, 0));
|
||||
AgentBalanceMap::set(&agents);
|
||||
}
|
||||
|
||||
pub fn set_delegator_balance(who: AccountId, amount: Balance) {
|
||||
let mut delegators = DelegatorBalanceMap::get();
|
||||
delegators.insert(who, amount);
|
||||
DelegatorBalanceMap::set(&delegators);
|
||||
}
|
||||
|
||||
pub fn on_slash(agent: AccountId, amount: Balance) {
|
||||
let mut agents = AgentBalanceMap::get();
|
||||
agents.get_mut(&agent).map(|(_, _, p)| *p += amount);
|
||||
AgentBalanceMap::set(&agents);
|
||||
}
|
||||
|
||||
fn on_withdraw(agent: AccountId, amount: Balance) {
|
||||
let mut agents = AgentBalanceMap::get();
|
||||
// if agent exists, add the amount to unclaimed withdrawals.
|
||||
agents.get_mut(&agent).map(|(_, u, _)| *u += amount);
|
||||
AgentBalanceMap::set(&agents);
|
||||
}
|
||||
}
|
||||
|
||||
impl DelegationMigrator for DelegateMock {
|
||||
type Balance = Balance;
|
||||
type AccountId = AccountId;
|
||||
fn migrate_nominator_to_agent(
|
||||
_agent: Agent<Self::AccountId>,
|
||||
_reward_account: &Self::AccountId,
|
||||
) -> DispatchResult {
|
||||
unimplemented!("not used in current unit tests")
|
||||
}
|
||||
|
||||
fn migrate_delegation(
|
||||
_agent: Agent<Self::AccountId>,
|
||||
_delegator: Delegator<Self::AccountId>,
|
||||
_value: Self::Balance,
|
||||
) -> DispatchResult {
|
||||
unimplemented!("not used in current unit tests")
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn force_kill_agent(_agent: Agent<Self::AccountId>) {
|
||||
unimplemented!("not used in current unit tests")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Runtime {
|
||||
type Nonce = u64;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = pezsp_runtime::traits::IdentityLookup<Self::AccountId>;
|
||||
type Block = Block;
|
||||
type AccountData = pezpallet_balances::AccountData<Balance>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static ExistentialDeposit: Balance = 5;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for Runtime {
|
||||
type Balance = Balance;
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
type FreezeIdentifier = RuntimeFreezeReason;
|
||||
type MaxFreezes = VariantCountOf<RuntimeFreezeReason>;
|
||||
type RuntimeFreezeReason = RuntimeFreezeReason;
|
||||
}
|
||||
|
||||
pub struct BalanceToU256;
|
||||
impl Convert<Balance, U256> for BalanceToU256 {
|
||||
fn convert(n: Balance) -> U256 {
|
||||
n.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct U256ToBalance;
|
||||
impl Convert<U256, Balance> for U256ToBalance {
|
||||
fn convert(n: U256) -> Balance {
|
||||
n.try_into().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RestrictMock;
|
||||
impl Contains<AccountId> for RestrictMock {
|
||||
fn contains(who: &AccountId) -> bool {
|
||||
RestrictedAccounts::get().contains(who)
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static PostUnbondingPoolsWindow: u32 = 2;
|
||||
pub static MaxMetadataLen: u32 = 2;
|
||||
pub static CheckLevel: u8 = 255;
|
||||
pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls");
|
||||
}
|
||||
|
||||
ord_parameter_types! {
|
||||
pub const Admin: u128 = 42;
|
||||
}
|
||||
|
||||
impl pools::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
type Currency = Balances;
|
||||
type RuntimeFreezeReason = RuntimeFreezeReason;
|
||||
type RewardCounter = RewardCounter;
|
||||
type BalanceToU256 = BalanceToU256;
|
||||
type U256ToBalance = U256ToBalance;
|
||||
type StakeAdapter = adapter::DelegateStake<Self, StakingMock, DelegateMock>;
|
||||
type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow;
|
||||
type PalletId = PoolsPalletId;
|
||||
type MaxMetadataLen = MaxMetadataLen;
|
||||
type MaxUnbonding = MaxUnbonding;
|
||||
type MaxPointsToBalance = pezframe_support::traits::ConstU8<10>;
|
||||
type AdminOrigin = EnsureSignedBy<Admin, AccountId>;
|
||||
type BlockNumberProvider = System;
|
||||
type Filter = RestrictMock;
|
||||
}
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<Runtime>;
|
||||
pezframe_support::construct_runtime!(
|
||||
pub enum Runtime {
|
||||
System: pezframe_system,
|
||||
Balances: pezpallet_balances,
|
||||
Pools: pools,
|
||||
}
|
||||
);
|
||||
|
||||
pub struct ExtBuilder {
|
||||
members: Vec<(AccountId, Balance)>,
|
||||
max_members: Option<u32>,
|
||||
max_members_per_pool: Option<u32>,
|
||||
global_max_commission: Option<Perbill>,
|
||||
}
|
||||
|
||||
impl Default for ExtBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
members: Default::default(),
|
||||
max_members: Some(4),
|
||||
max_members_per_pool: Some(3),
|
||||
global_max_commission: Some(Perbill::from_percent(90)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "fuzzing", allow(dead_code))]
|
||||
impl ExtBuilder {
|
||||
// Add members to pool 0.
|
||||
pub fn add_members(mut self, members: Vec<(AccountId, Balance)>) -> Self {
|
||||
self.members = members;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn ed(self, ed: Balance) -> Self {
|
||||
ExistentialDeposit::set(ed);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn min_bond(self, min: Balance) -> Self {
|
||||
StakingMinBond::set(min);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn min_join_bond(self, min: Balance) -> Self {
|
||||
MinJoinBondConfig::set(min);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_check(self, level: u8) -> Self {
|
||||
CheckLevel::set(level);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn max_members(mut self, max: Option<u32>) -> Self {
|
||||
self.max_members = max;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn max_members_per_pool(mut self, max: Option<u32>) -> Self {
|
||||
self.max_members_per_pool = max;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn global_max_commission(mut self, commission: Option<Perbill>) -> Self {
|
||||
self.global_max_commission = commission;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> pezsp_io::TestExternalities {
|
||||
pezsp_tracing::try_init_simple();
|
||||
let mut storage =
|
||||
pezframe_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
|
||||
|
||||
let _ = crate::GenesisConfig::<Runtime> {
|
||||
min_join_bond: MinJoinBondConfig::get(),
|
||||
min_create_bond: 2,
|
||||
max_pools: Some(2),
|
||||
max_members_per_pool: self.max_members_per_pool,
|
||||
max_members: self.max_members,
|
||||
global_max_commission: self.global_max_commission,
|
||||
}
|
||||
.assimilate_storage(&mut storage);
|
||||
|
||||
let mut ext = pezsp_io::TestExternalities::from(storage);
|
||||
|
||||
ext.execute_with(|| {
|
||||
// for events to be deposited.
|
||||
pezframe_system::Pallet::<Runtime>::set_block_number(1);
|
||||
|
||||
// make a pool
|
||||
let amount_to_bond = Pools::depositor_min_bond();
|
||||
Currency::set_balance(&10, amount_to_bond * 5);
|
||||
assert_ok!(Pools::create(RawOrigin::Signed(10).into(), amount_to_bond, 900, 901, 902));
|
||||
assert_ok!(Pools::set_metadata(RuntimeOrigin::signed(900), 1, vec![1, 1]));
|
||||
let last_pool = LastPoolId::<Runtime>::get();
|
||||
for (account_id, bonded) in self.members {
|
||||
<Runtime as Config>::Currency::set_balance(&account_id, bonded * 2);
|
||||
assert_ok!(Pools::join(RawOrigin::Signed(account_id).into(), bonded, last_pool));
|
||||
}
|
||||
});
|
||||
|
||||
ext
|
||||
}
|
||||
|
||||
pub fn build_and_execute(self, test: impl FnOnce()) {
|
||||
self.build().execute_with(|| {
|
||||
test();
|
||||
Pools::do_try_state(CheckLevel::get()).unwrap();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unsafe_set_state(pool_id: PoolId, state: PoolState) {
|
||||
BondedPools::<Runtime>::try_mutate(pool_id, |maybe_bonded_pool| {
|
||||
maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| {
|
||||
bonded_pool.state = state;
|
||||
})
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
storage PoolsEvents: u32 = 0;
|
||||
storage BalancesEvents: u32 = 0;
|
||||
}
|
||||
|
||||
/// Helper to run a specified amount of blocks.
|
||||
pub fn run_blocks(n: u64) {
|
||||
let current_block = System::block_number();
|
||||
System::run_to_block::<AllPalletsWithSystem>(n + current_block);
|
||||
}
|
||||
|
||||
/// All events of this pallet.
|
||||
pub fn pool_events_since_last_call() -> Vec<super::Event<Runtime>> {
|
||||
let events = System::events()
|
||||
.into_iter()
|
||||
.map(|r| r.event)
|
||||
.filter_map(|e| if let RuntimeEvent::Pools(inner) = e { Some(inner) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
let already_seen = PoolsEvents::get();
|
||||
PoolsEvents::set(&(events.len() as u32));
|
||||
events.into_iter().skip(already_seen as usize).collect()
|
||||
}
|
||||
|
||||
/// All events of the `Balances` pallet.
|
||||
pub fn balances_events_since_last_call() -> Vec<pezpallet_balances::Event<Runtime>> {
|
||||
let events = System::events()
|
||||
.into_iter()
|
||||
.map(|r| r.event)
|
||||
.filter_map(|e| if let RuntimeEvent::Balances(inner) = e { Some(inner) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
let already_seen = BalancesEvents::get();
|
||||
BalancesEvents::set(&(events.len() as u32));
|
||||
events.into_iter().skip(already_seen as usize).collect()
|
||||
}
|
||||
|
||||
/// Same as `fully_unbond`, in permissioned setting.
|
||||
pub fn fully_unbond_permissioned(member: AccountId) -> DispatchResult {
|
||||
let points = PoolMembers::<Runtime>::get(member)
|
||||
.map(|d| d.active_points())
|
||||
.unwrap_or_default();
|
||||
Pools::unbond(RuntimeOrigin::signed(member), member, points)
|
||||
}
|
||||
|
||||
pub fn pending_rewards_for_delegator(delegator: AccountId) -> Balance {
|
||||
let member = PoolMembers::<T>::get(delegator).unwrap();
|
||||
let bonded_pool = BondedPools::<T>::get(member.pool_id).unwrap();
|
||||
let reward_pool = RewardPools::<T>::get(member.pool_id).unwrap();
|
||||
|
||||
assert!(!bonded_pool.points.is_zero());
|
||||
|
||||
let commission = bonded_pool.commission.current();
|
||||
let current_rc = reward_pool
|
||||
.current_reward_counter(member.pool_id, bonded_pool.points, commission)
|
||||
.unwrap()
|
||||
.0;
|
||||
|
||||
member.pending_rewards(current_rc).unwrap_or_default()
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum RewardImbalance {
|
||||
// There is no reward deficit.
|
||||
Surplus(Balance),
|
||||
// There is a reward deficit.
|
||||
Deficit(Balance),
|
||||
}
|
||||
|
||||
pub fn pool_pending_rewards(pool: PoolId) -> Result<BalanceOf<T>, pezsp_runtime::DispatchError> {
|
||||
let bonded_pool = BondedPools::<T>::get(pool).ok_or(Error::<T>::PoolNotFound)?;
|
||||
let reward_pool = RewardPools::<T>::get(pool).ok_or(Error::<T>::PoolNotFound)?;
|
||||
|
||||
let current_rc = if !bonded_pool.points.is_zero() {
|
||||
let commission = bonded_pool.commission.current();
|
||||
reward_pool.current_reward_counter(pool, bonded_pool.points, commission)?.0
|
||||
} else {
|
||||
Default::default()
|
||||
};
|
||||
|
||||
Ok(PoolMembers::<T>::iter()
|
||||
.filter(|(_, d)| d.pool_id == pool)
|
||||
.map(|(_, d)| d.pending_rewards(current_rc).unwrap_or_default())
|
||||
.fold(0u32.into(), |acc: BalanceOf<T>, x| acc.saturating_add(x)))
|
||||
}
|
||||
|
||||
pub fn reward_imbalance(pool: PoolId) -> RewardImbalance {
|
||||
let pending_rewards = pool_pending_rewards(pool).expect("pool should exist");
|
||||
let current_balance = RewardPool::<Runtime>::current_balance(pool);
|
||||
|
||||
if pending_rewards > current_balance {
|
||||
RewardImbalance::Deficit(pending_rewards - current_balance)
|
||||
} else {
|
||||
RewardImbalance::Surplus(current_balance - pending_rewards)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_pool_balance(who: AccountId, amount: Balance) {
|
||||
StakingMock::set_bonded_balance(who, amount);
|
||||
DelegateMock::set_agent_balance(who, amount);
|
||||
}
|
||||
|
||||
pub fn member_delegation(who: AccountId) -> Balance {
|
||||
<T as Config>::StakeAdapter::member_delegation_balance(Member::from(who))
|
||||
.expect("who must be a pool member")
|
||||
}
|
||||
|
||||
pub fn pool_balance(id: PoolId) -> Balance {
|
||||
<T as Config>::StakeAdapter::total_balance(Pool::from(Pools::generate_bonded_account(id)))
|
||||
.expect("who must be a bonded pool account")
|
||||
}
|
||||
|
||||
pub fn add_to_restrict_list(who: &AccountId) {
|
||||
if !RestrictedAccounts::get().contains(who) {
|
||||
RestrictedAccounts::mutate(|l| l.push(*who));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_from_restrict_list(who: &AccountId) {
|
||||
RestrictedAccounts::mutate(|l| l.retain(|x| x != who));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn u256_to_balance_convert_works() {
|
||||
assert_eq!(U256ToBalance::convert(0u32.into()), Zero::zero());
|
||||
assert_eq!(U256ToBalance::convert(Balance::max_value().into()), Balance::max_value())
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn u256_to_balance_convert_panics_correctly() {
|
||||
U256ToBalance::convert(U256::from(Balance::max_value()).saturating_add(1u32.into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn balance_to_u256_convert_works() {
|
||||
assert_eq!(BalanceToU256::convert(0u32.into()), U256::zero());
|
||||
assert_eq!(BalanceToU256::convert(Balance::max_value()), Balance::max_value().into())
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
||||
[package]
|
||||
name = "pezpallet-nomination-pools-test-delegate-stake"
|
||||
version = "1.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
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 = { features = ["derive"], workspace = true, default-features = true }
|
||||
scale-info = { features = [
|
||||
"derive",
|
||||
], workspace = true, default-features = true }
|
||||
|
||||
pezsp-core = { workspace = true, default-features = true }
|
||||
pezsp-io = { workspace = true, default-features = true }
|
||||
pezsp-runtime = { workspace = true, default-features = true }
|
||||
pezsp-staking = { workspace = true, default-features = true }
|
||||
|
||||
pezframe-election-provider-support = { workspace = true, default-features = true }
|
||||
pezframe-support = { features = [
|
||||
"experimental",
|
||||
], workspace = true, default-features = true }
|
||||
pezframe-system = { workspace = true, default-features = true }
|
||||
|
||||
pezpallet-bags-list = { workspace = true, default-features = true }
|
||||
pezpallet-balances = { workspace = true, default-features = true }
|
||||
pezpallet-delegated-staking = { workspace = true, default-features = true }
|
||||
pezpallet-nomination-pools = { workspace = true, default-features = true }
|
||||
pezpallet-staking = { workspace = true, default-features = true }
|
||||
pezpallet-staking-reward-curve = { workspace = true, default-features = true }
|
||||
pezpallet-timestamp = { workspace = true, default-features = true }
|
||||
|
||||
log = { workspace = true, default-features = true }
|
||||
pezsp-tracing = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-election-provider-support/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-bags-list/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezpallet-delegated-staking/runtime-benchmarks",
|
||||
"pezpallet-nomination-pools/runtime-benchmarks",
|
||||
"pezpallet-staking-reward-curve/runtime-benchmarks",
|
||||
"pezpallet-staking/runtime-benchmarks",
|
||||
"pezpallet-timestamp/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"pezsp-staking/runtime-benchmarks",
|
||||
]
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,388 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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.
|
||||
|
||||
// Disable warnings for `TransferStake` being deprecated.
|
||||
#![allow(deprecated)]
|
||||
|
||||
use pezframe_election_provider_support::VoteWeight;
|
||||
use pezframe_support::{
|
||||
assert_ok, derive_impl,
|
||||
pezpallet_prelude::*,
|
||||
parameter_types,
|
||||
traits::{ConstU64, ConstU8, Nothing, VariantCountOf},
|
||||
PalletId,
|
||||
};
|
||||
use pezframe_system::EnsureRoot;
|
||||
use pezpallet_nomination_pools::{
|
||||
adapter::{Member, Pool, StakeStrategyType},
|
||||
BondType,
|
||||
};
|
||||
use pezsp_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(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Runtime {
|
||||
type Nonce = Nonce;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Block = Block;
|
||||
type AccountData = pezpallet_balances::AccountData<Balance>;
|
||||
}
|
||||
|
||||
impl pezpallet_timestamp::Config for Runtime {
|
||||
type Moment = u64;
|
||||
type OnTimestampSet = ();
|
||||
type MinimumPeriod = ConstU64<5>;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static ExistentialDeposit: Balance = 5;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for Runtime {
|
||||
type Balance = Balance;
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
type FreezeIdentifier = RuntimeFreezeReason;
|
||||
type MaxFreezes = VariantCountOf<RuntimeFreezeReason>;
|
||||
type RuntimeHoldReason = RuntimeHoldReason;
|
||||
type RuntimeFreezeReason = RuntimeFreezeReason;
|
||||
}
|
||||
|
||||
pezpallet_staking_reward_curve::build! {
|
||||
const I_NPOS: pezsp_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 pezsp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS;
|
||||
pub static BondingDuration: u32 = 3;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_staking::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_staking::Config for Runtime {
|
||||
type OldCurrency = Balances;
|
||||
type Currency = Balances;
|
||||
type UnixTime = pezpallet_timestamp::Pallet<Self>;
|
||||
type AdminOrigin = pezframe_system::EnsureRoot<Self::AccountId>;
|
||||
type BondingDuration = BondingDuration;
|
||||
type EraPayout = pezpallet_staking::ConvertCurve<RewardCurve>;
|
||||
type ElectionProvider =
|
||||
pezframe_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, (), ())>;
|
||||
type GenesisElectionProvider = Self::ElectionProvider;
|
||||
type VoterList = VoterList;
|
||||
type TargetList = pezpallet_staking::UseValidatorsMap<Self>;
|
||||
type EventListeners = (Pools, DelegatedStaking);
|
||||
type BenchmarkingConfig = pezpallet_staking::TestBenchmarkingConfig;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static BagThresholds: &'static [VoteWeight] = &[10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000];
|
||||
}
|
||||
|
||||
type VoterBagsListInstance = pezpallet_bags_list::Instance1;
|
||||
impl pezpallet_bags_list::Config<VoterBagsListInstance> for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
type BagThresholds = BagThresholds;
|
||||
type ScoreProvider = Staking;
|
||||
type Score = VoteWeight;
|
||||
type MaxAutoRebagPerBlock = ();
|
||||
}
|
||||
|
||||
pub struct BalanceToU256;
|
||||
impl Convert<Balance, pezsp_core::U256> for BalanceToU256 {
|
||||
fn convert(n: Balance) -> pezsp_core::U256 {
|
||||
n.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct U256ToBalance;
|
||||
impl Convert<pezsp_core::U256, Balance> for U256ToBalance {
|
||||
fn convert(n: pezsp_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 =
|
||||
pezpallet_nomination_pools::adapter::DelegateStake<Runtime, Staking, DelegatedStaking>;
|
||||
type TransferStake = pezpallet_nomination_pools::adapter::TransferStake<Runtime, Staking>;
|
||||
impl pezpallet_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: Pool<Self::AccountId>,
|
||||
member_account: Member<Self::AccountId>,
|
||||
) -> Self::Balance {
|
||||
if LegacyAdapter::get() {
|
||||
return TransferStake::transferable_balance(pool_account, member_account);
|
||||
}
|
||||
DelegateStake::transferable_balance(pool_account, member_account)
|
||||
}
|
||||
|
||||
fn total_balance(pool_account: Pool<Self::AccountId>) -> Option<Self::Balance> {
|
||||
if LegacyAdapter::get() {
|
||||
return TransferStake::total_balance(pool_account);
|
||||
}
|
||||
DelegateStake::total_balance(pool_account)
|
||||
}
|
||||
|
||||
fn member_delegation_balance(member_account: Member<Self::AccountId>) -> Option<Self::Balance> {
|
||||
if LegacyAdapter::get() {
|
||||
return TransferStake::member_delegation_balance(member_account);
|
||||
}
|
||||
DelegateStake::member_delegation_balance(member_account)
|
||||
}
|
||||
|
||||
fn pledge_bond(
|
||||
who: Member<Self::AccountId>,
|
||||
pool_account: Pool<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: Member<Self::AccountId>,
|
||||
pool_account: Pool<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 dissolve(pool_account: Pool<Self::AccountId>) -> DispatchResult {
|
||||
if LegacyAdapter::get() {
|
||||
return TransferStake::dissolve(pool_account);
|
||||
}
|
||||
DelegateStake::dissolve(pool_account)
|
||||
}
|
||||
|
||||
fn pending_slash(pool_account: Pool<Self::AccountId>) -> Self::Balance {
|
||||
if LegacyAdapter::get() {
|
||||
return TransferStake::pending_slash(pool_account);
|
||||
}
|
||||
DelegateStake::pending_slash(pool_account)
|
||||
}
|
||||
|
||||
fn member_slash(
|
||||
who: Member<Self::AccountId>,
|
||||
pool_account: Pool<Self::AccountId>,
|
||||
amount: Self::Balance,
|
||||
maybe_reporter: Option<Self::AccountId>,
|
||||
) -> 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: Pool<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: Pool<Self::AccountId>,
|
||||
delegator: Member<Self::AccountId>,
|
||||
value: Self::Balance,
|
||||
) -> DispatchResult {
|
||||
if LegacyAdapter::get() {
|
||||
return TransferStake::migrate_delegation(agent, delegator, value);
|
||||
}
|
||||
DelegateStake::migrate_delegation(agent, delegator, value)
|
||||
}
|
||||
}
|
||||
impl pezpallet_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<AccountId>;
|
||||
type BlockNumberProvider = System;
|
||||
type Filter = Nothing;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const DelegatedStakingPalletId: PalletId = PalletId(*b"py/dlstk");
|
||||
pub const SlashRewardFraction: Perbill = Perbill::from_percent(1);
|
||||
}
|
||||
impl pezpallet_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 = pezframe_system::mocking::MockBlock<Runtime>;
|
||||
|
||||
pezframe_support::construct_runtime!(
|
||||
pub enum Runtime {
|
||||
System: pezframe_system,
|
||||
Timestamp: pezpallet_timestamp,
|
||||
Balances: pezpallet_balances,
|
||||
Staking: pezpallet_staking,
|
||||
VoterList: pezpallet_bags_list::<Instance1>,
|
||||
Pools: pezpallet_nomination_pools,
|
||||
DelegatedStaking: pezpallet_delegated_staking,
|
||||
}
|
||||
);
|
||||
|
||||
pub fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
pezsp_tracing::try_init_simple();
|
||||
let mut storage = pezframe_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
|
||||
let _ = pezpallet_nomination_pools::GenesisConfig::<Runtime> {
|
||||
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 _ = pezpallet_balances::GenesisConfig::<Runtime> {
|
||||
balances: vec![(10, 100), (20, 100), (21, 100), (22, 100)],
|
||||
..Default::default()
|
||||
}
|
||||
.assimilate_storage(&mut storage)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = pezsp_io::TestExternalities::from(storage);
|
||||
|
||||
ext.execute_with(|| {
|
||||
// for events to be deposited.
|
||||
pezframe_system::Pallet::<Runtime>::set_block_number(1);
|
||||
|
||||
// set some limit for nominations.
|
||||
assert_ok!(Staking::set_staking_configs(
|
||||
RuntimeOrigin::root(),
|
||||
pezpallet_staking::ConfigOp::Set(10), // minimum nominator bond
|
||||
pezpallet_staking::ConfigOp::Noop,
|
||||
pezpallet_staking::ConfigOp::Noop,
|
||||
pezpallet_staking::ConfigOp::Noop,
|
||||
pezpallet_staking::ConfigOp::Noop,
|
||||
pezpallet_staking::ConfigOp::Noop,
|
||||
pezpallet_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<pezpallet_nomination_pools::Event<Runtime>> {
|
||||
let events = System::events()
|
||||
.into_iter()
|
||||
.map(|r| r.event)
|
||||
.filter_map(|e| if let RuntimeEvent::Pools(inner) = e { Some(inner) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
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<pezpallet_staking::Event<Runtime>> {
|
||||
let events = System::events()
|
||||
.into_iter()
|
||||
.map(|r| r.event)
|
||||
.filter_map(|e| if let RuntimeEvent::Staking(inner) = e { Some(inner) } else { None })
|
||||
.collect::<Vec<_>>();
|
||||
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<pezpallet_delegated_staking::Event<Runtime>> {
|
||||
let events = System::events()
|
||||
.into_iter()
|
||||
.map(|r| r.event)
|
||||
.filter_map(
|
||||
|e| if let RuntimeEvent::DelegatedStaking(inner) = e { Some(inner) } else { None },
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
let already_seen = ObservedEventsDelegatedStaking::get();
|
||||
ObservedEventsDelegatedStaking::set(events.len());
|
||||
events.into_iter().skip(already_seen).collect()
|
||||
}
|
||||
Reference in New Issue
Block a user