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:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
+518
View File
@@ -0,0 +1,518 @@
// 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.
//! # Scored Pool Pallet
//!
//! The pallet maintains a scored membership pool. Each entity in the
//! pool can be attributed a `Score`. From this pool a set `Members`
//! is constructed. This set contains the `MemberCount` highest
//! scoring entities. Unscored entities are never part of `Members`.
//!
//! If an entity wants to be part of the pool a deposit is required.
//! The deposit is returned when the entity withdraws or when it
//! is removed by an entity with the appropriate authority.
//!
//! Every `Period` blocks the set of `Members` is refreshed from the
//! highest scoring members in the pool and, no matter if changes
//! occurred, `T::MembershipChanged::set_members_sorted` is invoked.
//! On first load `T::MembershipInitialized::initialize_members` is
//! invoked with the initial `Members` set.
//!
//! It is possible to withdraw candidacy/resign your membership at any
//! time. If an entity is currently a member, this results in removal
//! from the `Pool` and `Members`; the entity is immediately replaced
//! by the next highest scoring candidate in the pool, if available.
//!
//! - [`Config`]
//! - [`Call`]
//! - [`Pallet`]
//!
//! ## Interface
//!
//! ### Public Functions
//!
//! - `submit_candidacy` - Submit candidacy to become a member. Requires a deposit.
//! - `withdraw_candidacy` - Withdraw candidacy. Deposit is returned.
//! - `score` - Attribute a quantitative score to an entity.
//! - `kick` - Remove an entity from the pool and members. Deposit is returned.
//! - `change_member_count` - Changes the amount of candidates taken into `Members`.
//!
//! ## Usage
//!
//! ```
//! use pezpallet_scored_pool::{self as scored_pool};
//!
//! #[pezframe_support::pallet]
//! pub mod pallet {
//! use super::*;
//! use pezframe_support::pezpallet_prelude::*;
//! use pezframe_system::pezpallet_prelude::*;
//!
//! #[pallet::pallet]
//! pub struct Pallet<T>(_);
//!
//! #[pallet::config]
//! pub trait Config: pezframe_system::Config + scored_pool::Config {}
//!
//! #[pallet::call]
//! impl<T: Config> Pallet<T> {
//! #[pallet::weight({0})]
//! pub fn candidate(origin: OriginFor<T>) -> DispatchResult {
//! let who = ensure_signed(origin)?;
//!
//! let _ = <scored_pool::Pallet<T>>::submit_candidacy(
//! T::RuntimeOrigin::from(Some(who.clone()).into())
//! );
//! Ok(())
//! }
//! }
//! }
//!
//! # fn main() { }
//! ```
//!
//! ## Dependencies
//!
//! This pallet depends on the [System pallet](../pezframe_system/index.html).
// Ensure we're `no_std` when compiling for Wasm.
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(test)]
// We do not declare all features used by `construct_runtime`
#[allow(unexpected_cfgs)]
mod mock;
#[cfg(test)]
mod tests;
extern crate alloc;
use alloc::vec::Vec;
use codec::{FullCodec, MaxEncodedLen};
use core::{cmp::Reverse, fmt::Debug};
use pezframe_support::{
ensure,
traits::{ChangeMembers, Currency, Get, InitializeMembers, ReservableCurrency},
BoundedVec,
};
pub use pallet::*;
use pezsp_runtime::traits::{AtLeast32Bit, StaticLookup, Zero};
type BalanceOf<T, I> =
<<T as Config<I>>::Currency as Currency<<T as pezframe_system::Config>::AccountId>>::Balance;
type PoolT<T, I> = BoundedVec<
(<T as pezframe_system::Config>::AccountId, Option<<T as Config<I>>::Score>),
<T as Config<I>>::MaximumMembers,
>;
type MembersT<T, I> =
BoundedVec<<T as pezframe_system::Config>::AccountId, <T as Config<I>>::MaximumMembers>;
type AccountIdLookupOf<T> = <<T as pezframe_system::Config>::Lookup as StaticLookup>::Source;
/// The enum is supplied when refreshing the members set.
/// Depending on the enum variant the corresponding associated
/// type function will be invoked.
enum ChangeReceiver {
/// Should call `T::MembershipInitialized`.
MembershipInitialized,
/// Should call `T::MembershipChanged`.
MembershipChanged,
}
#[pezframe_support::pallet]
pub mod pallet {
use super::*;
use pezframe_support::pezpallet_prelude::*;
use pezframe_system::pezpallet_prelude::*;
#[pallet::pallet]
pub struct Pallet<T, I = ()>(_);
#[pallet::config]
pub trait Config<I: 'static = ()>: pezframe_system::Config {
/// The currency used for deposits.
type Currency: Currency<Self::AccountId> + ReservableCurrency<Self::AccountId>;
/// Maximum members length allowed.
#[pallet::constant]
type MaximumMembers: Get<u32>;
/// The score attributed to a member or candidate.
type Score: AtLeast32Bit
+ Clone
+ Copy
+ Default
+ FullCodec
+ MaybeSerializeDeserialize
+ Debug
+ scale_info::TypeInfo
+ MaxEncodedLen;
/// The overarching event type.
#[allow(deprecated)]
type RuntimeEvent: From<Event<Self, I>>
+ IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
// The deposit which is reserved from candidates if they want to
// start a candidacy. The deposit gets returned when the candidacy is
// withdrawn or when the candidate is kicked.
#[pallet::constant]
type CandidateDeposit: Get<BalanceOf<Self, I>>;
/// Every `Period` blocks the `Members` are filled with the highest scoring
/// members in the `Pool`.
#[pallet::constant]
type Period: Get<BlockNumberFor<Self>>;
/// The receiver of the signal for when the membership has been initialized.
/// This happens pre-genesis and will usually be the same as `MembershipChanged`.
/// If you need to do something different on initialization, then you can change
/// this accordingly.
type MembershipInitialized: InitializeMembers<Self::AccountId>;
/// The receiver of the signal for when the members have changed.
type MembershipChanged: ChangeMembers<Self::AccountId>;
/// Allows a configurable origin type to set a score to a candidate in the pool.
type ScoreOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// Required origin for removing a member (though can always be Root).
/// Configurable origin which enables removing an entity. If the entity
/// is part of the `Members` it is immediately replaced by the next
/// highest scoring candidate, if available.
type KickOrigin: EnsureOrigin<Self::RuntimeOrigin>;
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config<I>, I: 'static = ()> {
/// The given member was removed. See the transaction for who.
MemberRemoved,
/// An entity has issued a candidacy. See the transaction for who.
CandidateAdded,
/// An entity withdrew candidacy. See the transaction for who.
CandidateWithdrew,
/// The candidacy was forcefully removed for an entity.
/// See the transaction for who.
CandidateKicked,
/// A score was attributed to the candidate.
/// See the transaction for who.
CandidateScored,
}
/// Error for the scored-pool pallet.
#[pallet::error]
pub enum Error<T, I = ()> {
/// Already a member.
AlreadyInPool,
/// Index out of bounds.
InvalidIndex,
/// Index does not match requested account.
WrongAccountIndex,
/// Number of members exceeds `MaximumMembers`.
TooManyMembers,
}
/// The current pool of candidates, stored as an ordered Bounded Vec
/// (ordered descending by score, `None` last, highest first).
#[pallet::storage]
#[pallet::getter(fn pool)]
pub(crate) type Pool<T: Config<I>, I: 'static = ()> = StorageValue<_, PoolT<T, I>, ValueQuery>;
/// A Map of the candidates. The information in this Map is redundant
/// to the information in the `Pool`. But the Map enables us to easily
/// check if a candidate is already in the pool, without having to
/// iterate over the entire pool (the `Pool` is not sorted by
/// `T::AccountId`, but by `T::Score` instead).
#[pallet::storage]
#[pallet::getter(fn candidate_exists)]
pub(crate) type CandidateExists<T: Config<I>, I: 'static = ()> =
StorageMap<_, Twox64Concat, T::AccountId, bool, ValueQuery>;
/// The current membership, stored as an ordered Vec.
#[pallet::storage]
#[pallet::getter(fn members)]
pub(crate) type Members<T: Config<I>, I: 'static = ()> =
StorageValue<_, MembersT<T, I>, ValueQuery>;
/// Size of the `Members` set.
#[pallet::storage]
#[pallet::getter(fn member_count)]
pub(crate) type MemberCount<T, I = ()> = StorageValue<_, u32, ValueQuery>;
#[pallet::genesis_config]
#[derive(pezframe_support::DefaultNoBound)]
pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
pub pool: PoolT<T, I>,
pub member_count: u32,
}
#[pallet::genesis_build]
impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
fn build(&self) {
let mut pool = self.pool.clone();
// reserve balance for each candidate in the pool.
// panicking here is ok, since this just happens one time, pre-genesis.
pool.iter().for_each(|(who, _)| {
T::Currency::reserve(who, T::CandidateDeposit::get())
.expect("balance too low to create candidacy");
<CandidateExists<T, I>>::insert(who, true);
});
// Sorts the `Pool` by score in a descending order. Entities which
// have a score of `None` are sorted to the end of the bounded vec.
pool.sort_by_key(|(_, maybe_score)| Reverse(maybe_score.unwrap_or_default()));
<Pallet<T, I>>::update_member_count(self.member_count)
.expect("Number of allowed members exceeded");
<Pool<T, I>>::put(&pool);
<Pallet<T, I>>::refresh_members(pool, ChangeReceiver::MembershipInitialized);
}
}
#[pallet::hooks]
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
/// Every `Period` blocks the `Members` set is refreshed from the
/// highest scoring members in the pool.
fn on_initialize(n: pezframe_system::pezpallet_prelude::BlockNumberFor<T>) -> Weight {
if n % T::Period::get() == Zero::zero() {
let pool = <Pool<T, I>>::get();
<Pallet<T, I>>::refresh_members(pool, ChangeReceiver::MembershipChanged);
}
Weight::zero()
}
}
#[pallet::call]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Add `origin` to the pool of candidates.
///
/// This results in `CandidateDeposit` being reserved from
/// the `origin` account. The deposit is returned once
/// candidacy is withdrawn by the candidate or the entity
/// is kicked by `KickOrigin`.
///
/// The dispatch origin of this function must be signed.
///
/// The `index` parameter of this function must be set to
/// the index of the transactor in the `Pool`.
#[pallet::call_index(0)]
#[pallet::weight({0})]
pub fn submit_candidacy(origin: OriginFor<T>) -> DispatchResult {
let who = ensure_signed(origin)?;
ensure!(!<CandidateExists<T, I>>::contains_key(&who), Error::<T, I>::AlreadyInPool);
let deposit = T::CandidateDeposit::get();
T::Currency::reserve(&who, deposit)?;
// can be inserted as last element in pool, since entities with
// `None` are always sorted to the end.
<Pool<T, I>>::try_append((who.clone(), Option::<<T as Config<I>>::Score>::None))
.map_err(|_| Error::<T, I>::TooManyMembers)?;
<CandidateExists<T, I>>::insert(&who, true);
Self::deposit_event(Event::<T, I>::CandidateAdded);
Ok(())
}
/// An entity withdraws candidacy and gets its deposit back.
///
/// If the entity is part of the `Members`, then the highest member
/// of the `Pool` that is not currently in `Members` is immediately
/// placed in the set instead.
///
/// The dispatch origin of this function must be signed.
///
/// The `index` parameter of this function must be set to
/// the index of the transactor in the `Pool`.
#[pallet::call_index(1)]
#[pallet::weight({0})]
pub fn withdraw_candidacy(origin: OriginFor<T>, index: u32) -> DispatchResult {
let who = ensure_signed(origin)?;
let pool = <Pool<T, I>>::get();
Self::ensure_index(&pool, &who, index)?;
Self::remove_member(pool, who, index)?;
Self::deposit_event(Event::<T, I>::CandidateWithdrew);
Ok(())
}
/// Kick a member `who` from the set.
///
/// May only be called from `T::KickOrigin`.
///
/// The `index` parameter of this function must be set to
/// the index of `dest` in the `Pool`.
#[pallet::call_index(2)]
#[pallet::weight({0})]
pub fn kick(
origin: OriginFor<T>,
dest: AccountIdLookupOf<T>,
index: u32,
) -> DispatchResult {
T::KickOrigin::ensure_origin(origin)?;
let who = T::Lookup::lookup(dest)?;
let pool = <Pool<T, I>>::get();
Self::ensure_index(&pool, &who, index)?;
Self::remove_member(pool, who, index)?;
Self::deposit_event(Event::<T, I>::CandidateKicked);
Ok(())
}
/// Score a member `who` with `score`.
///
/// May only be called from `T::ScoreOrigin`.
///
/// The `index` parameter of this function must be set to
/// the index of the `dest` in the `Pool`.
#[pallet::call_index(3)]
#[pallet::weight({0})]
pub fn score(
origin: OriginFor<T>,
dest: AccountIdLookupOf<T>,
index: u32,
score: T::Score,
) -> DispatchResult {
T::ScoreOrigin::ensure_origin(origin)?;
let who = T::Lookup::lookup(dest)?;
let mut pool = <Pool<T, I>>::get();
Self::ensure_index(&pool, &who, index)?;
pool.remove(index as usize);
// we binary search the pool (which is sorted descending by score).
// if there is already an element with `score`, we insert
// right before that. if not, the search returns a location
// where we can insert while maintaining order.
let item = (who, Some(score));
let location = pool
.binary_search_by_key(&Reverse(score), |(_, maybe_score)| {
Reverse(maybe_score.unwrap_or_default())
})
.unwrap_or_else(|l| l);
pool.try_insert(location, item).map_err(|_| Error::<T, I>::TooManyMembers)?;
<Pool<T, I>>::put(&pool);
Self::deposit_event(Event::<T, I>::CandidateScored);
Ok(())
}
/// Dispatchable call to change `MemberCount`.
///
/// This will only have an effect the next time a refresh happens
/// (this happens each `Period`).
///
/// May only be called from root.
#[pallet::call_index(4)]
#[pallet::weight({0})]
pub fn change_member_count(origin: OriginFor<T>, count: u32) -> DispatchResult {
ensure_root(origin)?;
Self::update_member_count(count).map_err(Into::into)
}
}
}
impl<T: Config<I>, I: 'static> Pallet<T, I> {
/// Fetches the `MemberCount` highest scoring members from
/// `Pool` and puts them into `Members`.
///
/// The `notify` parameter is used to deduct which associated
/// type function to invoke at the end of the method.
fn refresh_members(pool: PoolT<T, I>, notify: ChangeReceiver) {
let count = MemberCount::<T, I>::get();
let old_members = <Members<T, I>>::get();
let new_members: Vec<T::AccountId> = pool
.into_iter()
.filter(|(_, score)| score.is_some())
.take(count as usize)
.map(|(account_id, _)| account_id)
.collect();
// It's safe to truncate_from at this point since MemberCount
// is verified that it does not exceed the MaximumMembers value
let mut new_members_bounded: MembersT<T, I> = BoundedVec::truncate_from(new_members);
new_members_bounded.sort();
<Members<T, I>>::put(&new_members_bounded);
match notify {
ChangeReceiver::MembershipInitialized =>
T::MembershipInitialized::initialize_members(&new_members_bounded),
ChangeReceiver::MembershipChanged =>
T::MembershipChanged::set_members_sorted(&new_members_bounded[..], &old_members[..]),
}
}
/// Removes an entity `remove` at `index` from the `Pool`.
///
/// If the entity is a member it is also removed from `Members` and
/// the deposit is returned.
fn remove_member(
mut pool: PoolT<T, I>,
remove: T::AccountId,
index: u32,
) -> Result<(), Error<T, I>> {
// all callers of this function in this pallet also check
// the index for validity before calling this function.
// nevertheless we check again here, to assert that there was
// no mistake when invoking this sensible function.
Self::ensure_index(&pool, &remove, index)?;
pool.remove(index as usize);
<Pool<T, I>>::put(&pool);
// remove from set, if it was in there
let members = <Members<T, I>>::get();
if members.binary_search(&remove).is_ok() {
Self::refresh_members(pool, ChangeReceiver::MembershipChanged);
}
<CandidateExists<T, I>>::remove(&remove);
T::Currency::unreserve(&remove, T::CandidateDeposit::get());
Self::deposit_event(Event::<T, I>::MemberRemoved);
Ok(())
}
/// Checks if `index` is a valid number and if the element found
/// at `index` in `Pool` is equal to `who`.
fn ensure_index(pool: &PoolT<T, I>, who: &T::AccountId, index: u32) -> Result<(), Error<T, I>> {
ensure!(index < pool.len() as u32, Error::<T, I>::InvalidIndex);
let (index_who, _index_score) = &pool[index as usize];
ensure!(index_who == who, Error::<T, I>::WrongAccountIndex);
Ok(())
}
/// Make sure the new member count value does not exceed the MaximumMembers
fn update_member_count(new_member_count: u32) -> Result<(), Error<T, I>> {
ensure!(new_member_count <= T::MaximumMembers::get(), Error::<T, I>::TooManyMembers);
<MemberCount<T, I>>::put(new_member_count);
Ok(())
}
}
+133
View File
@@ -0,0 +1,133 @@
// 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.
//! Test utilities
use super::*;
use crate as pezpallet_scored_pool;
use pezframe_support::{
construct_runtime, derive_impl, ord_parameter_types, parameter_types,
traits::{ConstU32, ConstU64},
};
use pezframe_system::EnsureSignedBy;
use pezsp_runtime::{bounded_vec, BuildStorage};
type Block = pezframe_system::mocking::MockBlock<Test>;
construct_runtime!(
pub enum Test
{
System: pezframe_system,
Balances: pezpallet_balances,
ScoredPool: pezpallet_scored_pool,
}
);
parameter_types! {
pub const CandidateDeposit: u64 = 25;
}
ord_parameter_types! {
pub const KickOrigin: u64 = 2;
pub const ScoreOrigin: u64 = 3;
}
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
impl pezframe_system::Config for Test {
type Block = Block;
type AccountData = pezpallet_balances::AccountData<u64>;
}
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
impl pezpallet_balances::Config for Test {
type AccountStore = System;
}
parameter_types! {
pub static MembersTestValue: BoundedVec<u64,ConstU32<10_u32>> = bounded_vec![0,10];
}
pub struct TestChangeMembers;
impl ChangeMembers<u64> for TestChangeMembers {
fn change_members_sorted(incoming: &[u64], outgoing: &[u64], new: &[u64]) {
let mut old_plus_incoming = MembersTestValue::get().into_inner();
old_plus_incoming.extend_from_slice(incoming);
old_plus_incoming.sort();
let mut new_plus_outgoing = new.to_vec();
new_plus_outgoing.extend_from_slice(outgoing);
new_plus_outgoing.sort();
assert_eq!(old_plus_incoming, new_plus_outgoing);
MembersTestValue::set(<BoundedVec<u64, ConstU32<10_u32>>>::truncate_from(new.to_vec()));
}
}
impl InitializeMembers<u64> for TestChangeMembers {
fn initialize_members(new_members: &[u64]) {
MembersTestValue::set(<BoundedVec<u64, ConstU32<10_u32>>>::truncate_from(
new_members.to_vec(),
));
}
}
impl Config for Test {
type RuntimeEvent = RuntimeEvent;
type KickOrigin = EnsureSignedBy<KickOrigin, u64>;
type MembershipInitialized = TestChangeMembers;
type MembershipChanged = TestChangeMembers;
type Currency = Balances;
type CandidateDeposit = CandidateDeposit;
type Period = ConstU64<4>;
type Score = u64;
type ScoreOrigin = EnsureSignedBy<ScoreOrigin, u64>;
type MaximumMembers = ConstU32<10>;
}
pub fn new_test_ext() -> pezsp_io::TestExternalities {
let mut t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
let mut balances = vec![];
for i in 1..31 {
balances.push((i, 500_000));
}
balances.push((31, 500_000));
balances.push((40, 500_000));
balances.push((99, 1));
pezpallet_balances::GenesisConfig::<Test> { balances, ..Default::default() }
.assimilate_storage(&mut t)
.unwrap();
pezpallet_scored_pool::GenesisConfig::<Test> {
pool: bounded_vec![(10, Some(1)), (20, Some(2)), (31, Some(2)), (40, Some(3)), (5, None)],
member_count: 2,
}
.assimilate_storage(&mut t)
.unwrap();
t.into()
}
/// Fetch an entity from the pool, if existent.
pub fn fetch_from_pool(who: u64) -> Option<(u64, Option<u64>)> {
<Pallet<Test>>::pool().into_iter().find(|item| item.0 == who)
}
/// Find an entity in the pool.
/// Returns its position in the `Pool` vec, if existent.
pub fn find_in_pool(who: u64) -> Option<usize> {
<Pallet<Test>>::pool().into_iter().position(|item| item.0 == who)
}
@@ -0,0 +1,321 @@
// 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.
//! Tests for the pallet.
use super::*;
use mock::*;
use pezframe_support::{assert_noop, assert_ok, traits::OnInitialize};
use pezsp_runtime::traits::BadOrigin;
type ScoredPool = Pallet<Test>;
type System = pezframe_system::Pallet<Test>;
type Balances = pezpallet_balances::Pallet<Test>;
#[test]
fn query_membership_works() {
new_test_ext().execute_with(|| {
assert_eq!(ScoredPool::members(), vec![20, 40]);
assert_eq!(Balances::reserved_balance(31), CandidateDeposit::get());
assert_eq!(Balances::reserved_balance(40), CandidateDeposit::get());
assert_eq!(MembersTestValue::get().clone(), vec![20, 40]);
});
}
#[test]
fn submit_candidacy_must_not_work() {
new_test_ext().execute_with(|| {
assert_noop!(
ScoredPool::submit_candidacy(RuntimeOrigin::signed(99)),
pezpallet_balances::Error::<Test, _>::InsufficientBalance,
);
assert_noop!(
ScoredPool::submit_candidacy(RuntimeOrigin::signed(40)),
Error::<Test, _>::AlreadyInPool
);
});
}
#[test]
fn submit_candidacy_works() {
new_test_ext().execute_with(|| {
// given
let who = 15;
// when
assert_ok!(ScoredPool::submit_candidacy(RuntimeOrigin::signed(who)));
assert_eq!(fetch_from_pool(15), Some((who, None)));
// then
assert_eq!(Balances::reserved_balance(who), CandidateDeposit::get());
});
}
#[test]
fn scoring_works() {
new_test_ext().execute_with(|| {
// given
let who = 15;
let score = 99;
assert_ok!(ScoredPool::submit_candidacy(RuntimeOrigin::signed(who)));
// when
let index = find_in_pool(who).expect("entity must be in pool") as u32;
assert_ok!(ScoredPool::score(RuntimeOrigin::signed(ScoreOrigin::get()), who, index, score));
// then
assert_eq!(fetch_from_pool(who), Some((who, Some(score))));
assert_eq!(find_in_pool(who), Some(0)); // must be first element, since highest scored
});
}
#[test]
fn scoring_same_element_with_same_score_works() {
new_test_ext().execute_with(|| {
// given
let who = 31;
let index = find_in_pool(who).expect("entity must be in pool") as u32;
let score = 2;
// when
assert_ok!(ScoredPool::score(RuntimeOrigin::signed(ScoreOrigin::get()), who, index, score));
// then
assert_eq!(fetch_from_pool(who), Some((who, Some(score))));
// must have been inserted right before the `20` element which is
// of the same score as `31`. so sort order is maintained.
assert_eq!(find_in_pool(who), Some(1));
});
}
#[test]
fn kicking_works_only_for_authorized() {
new_test_ext().execute_with(|| {
let who = 40;
let index = find_in_pool(who).expect("entity must be in pool") as u32;
assert_noop!(ScoredPool::kick(RuntimeOrigin::signed(99), who, index), BadOrigin);
});
}
#[test]
fn kicking_works() {
new_test_ext().execute_with(|| {
// given
let who = 40;
assert_eq!(Balances::reserved_balance(who), CandidateDeposit::get());
assert_eq!(find_in_pool(who), Some(0));
// when
let index = find_in_pool(who).expect("entity must be in pool") as u32;
assert_ok!(ScoredPool::kick(RuntimeOrigin::signed(KickOrigin::get()), who, index));
// then
assert_eq!(find_in_pool(who), None);
assert_eq!(ScoredPool::members(), vec![20, 31]);
assert_eq!(MembersTestValue::get().clone(), ScoredPool::members());
assert_eq!(Balances::reserved_balance(who), 0); // deposit must have been returned
});
}
#[test]
fn unscored_entities_must_not_be_used_for_filling_members() {
new_test_ext().execute_with(|| {
// given
// we submit a candidacy, score will be `None`
assert_ok!(ScoredPool::submit_candidacy(RuntimeOrigin::signed(15)));
// when
// we remove every scored member
ScoredPool::pool().into_iter().for_each(|(who, score)| {
if let Some(_) = score {
let index = find_in_pool(who).expect("entity must be in pool") as u32;
assert_ok!(ScoredPool::kick(RuntimeOrigin::signed(KickOrigin::get()), who, index));
}
});
// then
// the `None` candidates should not have been filled in
assert!(ScoredPool::members().is_empty());
assert_eq!(MembersTestValue::get().clone(), ScoredPool::members());
});
}
#[test]
fn refreshing_works() {
new_test_ext().execute_with(|| {
// given
let who = 15;
assert_ok!(ScoredPool::submit_candidacy(RuntimeOrigin::signed(who)));
let index = find_in_pool(who).expect("entity must be in pool") as u32;
assert_ok!(ScoredPool::score(RuntimeOrigin::signed(ScoreOrigin::get()), who, index, 99));
// when
ScoredPool::refresh_members(ScoredPool::pool(), ChangeReceiver::MembershipChanged);
// then
assert_eq!(ScoredPool::members(), vec![15, 40]);
assert_eq!(MembersTestValue::get().clone(), ScoredPool::members());
});
}
#[test]
fn refreshing_happens_every_period() {
new_test_ext().execute_with(|| {
// given
System::set_block_number(1);
assert_ok!(ScoredPool::submit_candidacy(RuntimeOrigin::signed(15)));
let index = find_in_pool(15).expect("entity must be in pool") as u32;
assert_ok!(ScoredPool::score(RuntimeOrigin::signed(ScoreOrigin::get()), 15, index, 99));
assert_eq!(ScoredPool::members(), vec![20, 40]);
// when
System::set_block_number(4);
ScoredPool::on_initialize(4);
// then
assert_eq!(ScoredPool::members(), vec![15, 40]);
assert_eq!(MembersTestValue::get().clone(), ScoredPool::members());
});
}
#[test]
fn withdraw_candidacy_must_only_work_for_members() {
new_test_ext().execute_with(|| {
let who = 77;
let index = 0;
assert_noop!(
ScoredPool::withdraw_candidacy(RuntimeOrigin::signed(who), index),
Error::<Test, _>::WrongAccountIndex
);
});
}
#[test]
fn oob_index_should_abort() {
new_test_ext().execute_with(|| {
let who = 40;
let oob_index = ScoredPool::pool().len() as u32;
assert_noop!(
ScoredPool::withdraw_candidacy(RuntimeOrigin::signed(who), oob_index),
Error::<Test, _>::InvalidIndex
);
assert_noop!(
ScoredPool::score(RuntimeOrigin::signed(ScoreOrigin::get()), who, oob_index, 99),
Error::<Test, _>::InvalidIndex
);
assert_noop!(
ScoredPool::kick(RuntimeOrigin::signed(KickOrigin::get()), who, oob_index),
Error::<Test, _>::InvalidIndex
);
});
}
#[test]
fn index_mismatches_should_abort() {
new_test_ext().execute_with(|| {
let who = 40;
let index = 3;
assert_noop!(
ScoredPool::withdraw_candidacy(RuntimeOrigin::signed(who), index),
Error::<Test, _>::WrongAccountIndex
);
assert_noop!(
ScoredPool::score(RuntimeOrigin::signed(ScoreOrigin::get()), who, index, 99),
Error::<Test, _>::WrongAccountIndex
);
assert_noop!(
ScoredPool::kick(RuntimeOrigin::signed(KickOrigin::get()), who, index),
Error::<Test, _>::WrongAccountIndex
);
});
}
#[test]
fn withdraw_unscored_candidacy_must_work() {
new_test_ext().execute_with(|| {
// given
let who = 5;
// when
let index = find_in_pool(who).expect("entity must be in pool") as u32;
assert_ok!(ScoredPool::withdraw_candidacy(RuntimeOrigin::signed(who), index));
// then
assert_eq!(fetch_from_pool(5), None);
});
}
#[test]
fn withdraw_scored_candidacy_must_work() {
new_test_ext().execute_with(|| {
// given
let who = 40;
assert_eq!(Balances::reserved_balance(who), CandidateDeposit::get());
// when
let index = find_in_pool(who).expect("entity must be in pool") as u32;
assert_ok!(ScoredPool::withdraw_candidacy(RuntimeOrigin::signed(who), index));
// then
assert_eq!(fetch_from_pool(who), None);
assert_eq!(ScoredPool::members(), vec![20, 31]);
assert_eq!(Balances::reserved_balance(who), 0);
});
}
#[test]
fn candidacy_resubmitting_works() {
new_test_ext().execute_with(|| {
// given
let who = 15;
// when
assert_ok!(ScoredPool::submit_candidacy(RuntimeOrigin::signed(who)));
assert_eq!(ScoredPool::candidate_exists(who), true);
let index = find_in_pool(who).expect("entity must be in pool") as u32;
assert_ok!(ScoredPool::withdraw_candidacy(RuntimeOrigin::signed(who), index));
assert_eq!(ScoredPool::candidate_exists(who), false);
assert_ok!(ScoredPool::submit_candidacy(RuntimeOrigin::signed(who)));
// then
assert_eq!(ScoredPool::candidate_exists(who), true);
});
}
#[test]
fn pool_candidates_exceeded() {
new_test_ext().execute_with(|| {
for i in [1, 2, 3, 4, 6] {
let who = i as u64;
assert_ok!(ScoredPool::submit_candidacy(RuntimeOrigin::signed(who)));
let index = find_in_pool(who).expect("entity must be in pool") as u32;
assert_ok!(ScoredPool::score(
RuntimeOrigin::signed(ScoreOrigin::get()),
who,
index,
99
));
}
assert_noop!(
ScoredPool::submit_candidacy(RuntimeOrigin::signed(8)),
Error::<Test, _>::TooManyMembers
);
});
}