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,38 @@
|
||||
[package]
|
||||
name = "pezpallet-salary"
|
||||
version = "13.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Paymaster"
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
frame = { workspace = true, features = ["runtime"] }
|
||||
log = { workspace = true }
|
||||
pezpallet-ranked-collective = { optional = true, workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"frame/std",
|
||||
"log/std",
|
||||
"pezpallet-ranked-collective/std",
|
||||
"scale-info/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"frame/runtime-benchmarks",
|
||||
"pezpallet-ranked-collective/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = ["frame/try-runtime", "pezpallet-ranked-collective?/try-runtime"]
|
||||
@@ -0,0 +1,3 @@
|
||||
# Salary
|
||||
|
||||
Make periodic payment to members of a ranked collective according to rank.
|
||||
@@ -0,0 +1,180 @@
|
||||
// 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.
|
||||
|
||||
//! Salary pallet benchmarking.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use super::*;
|
||||
use crate::Pallet as Salary;
|
||||
|
||||
use frame::benchmarking::prelude::*;
|
||||
const SEED: u32 = 0;
|
||||
|
||||
fn ensure_member_with_salary<T: Config<I>, I: 'static>(who: &T::AccountId) {
|
||||
// induct if not a member.
|
||||
if T::Members::rank_of(who).is_none() {
|
||||
T::Members::induct(who).unwrap();
|
||||
}
|
||||
// promote until they have a salary.
|
||||
for _ in 0..255 {
|
||||
let r = T::Members::rank_of(who).expect("prior guard ensures `who` is a member; qed");
|
||||
if !T::Salary::get_salary(r, &who).is_zero() {
|
||||
break;
|
||||
}
|
||||
T::Members::promote(who).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[instance_benchmarks]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn init() {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller.clone()));
|
||||
|
||||
assert!(Salary::<T, I>::status().is_some());
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn bump() {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
Salary::<T, I>::init(RawOrigin::Signed(caller.clone()).into()).unwrap();
|
||||
System::<T>::set_block_number(System::<T>::block_number() + Salary::<T, I>::cycle_period());
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller.clone()));
|
||||
|
||||
assert_eq!(Salary::<T, I>::status().unwrap().cycle_index, 1u32.into());
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn induct() {
|
||||
let caller = whitelisted_caller();
|
||||
ensure_member_with_salary::<T, I>(&caller);
|
||||
Salary::<T, I>::init(RawOrigin::Signed(caller.clone()).into()).unwrap();
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller.clone()));
|
||||
|
||||
assert!(Salary::<T, I>::last_active(&caller).is_ok());
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn register() {
|
||||
let caller = whitelisted_caller();
|
||||
ensure_member_with_salary::<T, I>(&caller);
|
||||
Salary::<T, I>::init(RawOrigin::Signed(caller.clone()).into()).unwrap();
|
||||
Salary::<T, I>::induct(RawOrigin::Signed(caller.clone()).into()).unwrap();
|
||||
System::<T>::set_block_number(System::<T>::block_number() + Salary::<T, I>::cycle_period());
|
||||
Salary::<T, I>::bump(RawOrigin::Signed(caller.clone()).into()).unwrap();
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller.clone()));
|
||||
|
||||
assert_eq!(Salary::<T, I>::last_active(&caller).unwrap(), 1u32.into());
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn payout() {
|
||||
let caller = whitelisted_caller();
|
||||
ensure_member_with_salary::<T, I>(&caller);
|
||||
Salary::<T, I>::init(RawOrigin::Signed(caller.clone()).into()).unwrap();
|
||||
Salary::<T, I>::induct(RawOrigin::Signed(caller.clone()).into()).unwrap();
|
||||
System::<T>::set_block_number(System::<T>::block_number() + Salary::<T, I>::cycle_period());
|
||||
Salary::<T, I>::bump(RawOrigin::Signed(caller.clone()).into()).unwrap();
|
||||
System::<T>::set_block_number(System::<T>::block_number() + T::RegistrationPeriod::get());
|
||||
|
||||
let salary = T::Salary::get_salary(T::Members::rank_of(&caller).unwrap(), &caller);
|
||||
T::Paymaster::ensure_successful(&caller, (), salary);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller.clone()));
|
||||
|
||||
match Claimant::<T, I>::get(&caller) {
|
||||
Some(ClaimantStatus { last_active, status: Attempted { id, .. } }) => {
|
||||
assert_eq!(last_active, 1u32.into());
|
||||
assert_ne!(T::Paymaster::check_payment(id), PaymentStatus::Failure);
|
||||
},
|
||||
_ => panic!("No claim made"),
|
||||
}
|
||||
assert!(Salary::<T, I>::payout(RawOrigin::Signed(caller.clone()).into()).is_err());
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn payout_other() {
|
||||
let caller = whitelisted_caller();
|
||||
ensure_member_with_salary::<T, I>(&caller);
|
||||
Salary::<T, I>::init(RawOrigin::Signed(caller.clone()).into()).unwrap();
|
||||
Salary::<T, I>::induct(RawOrigin::Signed(caller.clone()).into()).unwrap();
|
||||
System::<T>::set_block_number(System::<T>::block_number() + Salary::<T, I>::cycle_period());
|
||||
Salary::<T, I>::bump(RawOrigin::Signed(caller.clone()).into()).unwrap();
|
||||
System::<T>::set_block_number(System::<T>::block_number() + T::RegistrationPeriod::get());
|
||||
|
||||
let salary = T::Salary::get_salary(T::Members::rank_of(&caller).unwrap(), &caller);
|
||||
let recipient: T::AccountId = account("recipient", 0, SEED);
|
||||
T::Paymaster::ensure_successful(&recipient, (), salary);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller.clone()), recipient.clone());
|
||||
|
||||
match Claimant::<T, I>::get(&caller) {
|
||||
Some(ClaimantStatus { last_active, status: Attempted { id, .. } }) => {
|
||||
assert_eq!(last_active, 1u32.into());
|
||||
assert_ne!(T::Paymaster::check_payment(id), PaymentStatus::Failure);
|
||||
},
|
||||
_ => panic!("No claim made"),
|
||||
}
|
||||
assert!(Salary::<T, I>::payout(RawOrigin::Signed(caller.clone()).into()).is_err());
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn check_payment() {
|
||||
let caller = whitelisted_caller();
|
||||
ensure_member_with_salary::<T, I>(&caller);
|
||||
Salary::<T, I>::init(RawOrigin::Signed(caller.clone()).into()).unwrap();
|
||||
Salary::<T, I>::induct(RawOrigin::Signed(caller.clone()).into()).unwrap();
|
||||
System::<T>::set_block_number(System::<T>::block_number() + Salary::<T, I>::cycle_period());
|
||||
Salary::<T, I>::bump(RawOrigin::Signed(caller.clone()).into()).unwrap();
|
||||
System::<T>::set_block_number(System::<T>::block_number() + T::RegistrationPeriod::get());
|
||||
|
||||
let salary = T::Salary::get_salary(T::Members::rank_of(&caller).unwrap(), &caller);
|
||||
let recipient: T::AccountId = account("recipient", 0, SEED);
|
||||
T::Paymaster::ensure_successful(&recipient, (), salary);
|
||||
Salary::<T, I>::payout(RawOrigin::Signed(caller.clone()).into()).unwrap();
|
||||
let id = match Claimant::<T, I>::get(&caller).unwrap().status {
|
||||
Attempted { id, .. } => id,
|
||||
_ => panic!("No claim made"),
|
||||
};
|
||||
T::Paymaster::ensure_concluded(id);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller.clone()));
|
||||
|
||||
assert!(!matches!(Claimant::<T, I>::get(&caller).unwrap().status, Attempted { .. }));
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite! {
|
||||
Salary,
|
||||
crate::tests::unit::new_test_ext(),
|
||||
crate::tests::unit::Test,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,478 @@
|
||||
// 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.
|
||||
|
||||
//! Make periodic payment to members of a ranked collective according to rank.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use core::marker::PhantomData;
|
||||
use frame::{
|
||||
prelude::*,
|
||||
traits::tokens::{GetSalary, Pay, PaymentStatus},
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
mod benchmarking;
|
||||
pub mod weights;
|
||||
|
||||
pub use pallet::*;
|
||||
pub use weights::WeightInfo;
|
||||
|
||||
/// Payroll cycle.
|
||||
pub type Cycle = u32;
|
||||
|
||||
/// The status of the pallet instance.
|
||||
#[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)]
|
||||
pub struct StatusType<CycleIndex, BlockNumber, Balance> {
|
||||
/// The index of the "current cycle" (i.e. the last cycle being processed).
|
||||
cycle_index: CycleIndex,
|
||||
/// The first block of the "current cycle" (i.e. the last cycle being processed).
|
||||
cycle_start: BlockNumber,
|
||||
/// The total budget available for all payments in the current cycle.
|
||||
budget: Balance,
|
||||
/// The total amount of the payments registered in the current cycle.
|
||||
total_registrations: Balance,
|
||||
/// The total amount of unregistered payments which have been made in the current cycle.
|
||||
total_unregistered_paid: Balance,
|
||||
}
|
||||
|
||||
/// The state of a specific payment claim.
|
||||
#[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)]
|
||||
pub enum ClaimState<Balance, Id> {
|
||||
/// No claim recorded.
|
||||
Nothing,
|
||||
/// Amount reserved when last active.
|
||||
Registered(Balance),
|
||||
/// Amount attempted to be paid when last active as well as the identity of the payment.
|
||||
Attempted { registered: Option<Balance>, id: Id, amount: Balance },
|
||||
}
|
||||
|
||||
use ClaimState::*;
|
||||
|
||||
/// The status of a single payee/claimant.
|
||||
#[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)]
|
||||
pub struct ClaimantStatus<CycleIndex, Balance, Id> {
|
||||
/// The most recent cycle in which the claimant was active.
|
||||
last_active: CycleIndex,
|
||||
/// The state of the payment/claim with in the above cycle.
|
||||
status: ClaimState<Balance, Id>,
|
||||
}
|
||||
|
||||
#[frame::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config<I: 'static = ()>: pezframe_system::Config {
|
||||
/// Weight information for extrinsics in this pallet.
|
||||
type WeightInfo: WeightInfo;
|
||||
|
||||
/// The runtime event type.
|
||||
#[allow(deprecated)]
|
||||
type RuntimeEvent: From<Event<Self, I>>
|
||||
+ IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
|
||||
|
||||
/// Means by which we can make payments to accounts. This also defines the currency and the
|
||||
/// balance which we use to denote that currency.
|
||||
type Paymaster: Pay<Beneficiary = <Self as pezframe_system::Config>::AccountId, AssetKind = ()>;
|
||||
|
||||
/// The current membership of payees.
|
||||
type Members: RankedMembers<AccountId = <Self as pezframe_system::Config>::AccountId>;
|
||||
|
||||
/// The maximum payout to be made for a single period to an active member of the given rank.
|
||||
///
|
||||
/// The benchmarks require that this be non-zero for some rank at most 255.
|
||||
type Salary: GetSalary<
|
||||
<Self::Members as RankedMembers>::Rank,
|
||||
Self::AccountId,
|
||||
<Self::Paymaster as Pay>::Balance,
|
||||
>;
|
||||
|
||||
/// The number of blocks within a cycle which accounts have to register their intent to
|
||||
/// claim.
|
||||
///
|
||||
/// The number of blocks between sequential payout cycles is the sum of this and
|
||||
/// `PayoutPeriod`.
|
||||
#[pallet::constant]
|
||||
type RegistrationPeriod: Get<BlockNumberFor<Self>>;
|
||||
|
||||
/// The number of blocks within a cycle which accounts have to claim the payout.
|
||||
///
|
||||
/// The number of blocks between sequential payout cycles is the sum of this and
|
||||
/// `RegistrationPeriod`.
|
||||
#[pallet::constant]
|
||||
type PayoutPeriod: Get<BlockNumberFor<Self>>;
|
||||
|
||||
/// The total budget per cycle.
|
||||
///
|
||||
/// This may change over the course of a cycle without any problem.
|
||||
#[pallet::constant]
|
||||
type Budget: Get<BalanceOf<Self, I>>;
|
||||
}
|
||||
|
||||
pub type CycleIndexOf<T> = BlockNumberFor<T>;
|
||||
pub type BalanceOf<T, I> = <<T as Config<I>>::Paymaster as Pay>::Balance;
|
||||
pub type IdOf<T, I> = <<T as Config<I>>::Paymaster as Pay>::Id;
|
||||
pub type StatusOf<T, I> = StatusType<CycleIndexOf<T>, BlockNumberFor<T>, BalanceOf<T, I>>;
|
||||
pub type ClaimantStatusOf<T, I> = ClaimantStatus<CycleIndexOf<T>, BalanceOf<T, I>, IdOf<T, I>>;
|
||||
|
||||
/// The overall status of the system.
|
||||
#[pallet::storage]
|
||||
pub type Status<T: Config<I>, I: 'static = ()> = StorageValue<_, StatusOf<T, I>, OptionQuery>;
|
||||
|
||||
/// The status of a claimant.
|
||||
#[pallet::storage]
|
||||
pub type Claimant<T: Config<I>, I: 'static = ()> =
|
||||
StorageMap<_, Twox64Concat, T::AccountId, ClaimantStatusOf<T, I>, OptionQuery>;
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config<I>, I: 'static = ()> {
|
||||
/// A member is inducted into the payroll.
|
||||
Inducted { who: T::AccountId },
|
||||
/// A member registered for a payout.
|
||||
Registered { who: T::AccountId, amount: BalanceOf<T, I> },
|
||||
/// A payment happened.
|
||||
Paid {
|
||||
who: T::AccountId,
|
||||
beneficiary: T::AccountId,
|
||||
amount: BalanceOf<T, I>,
|
||||
id: <T::Paymaster as Pay>::Id,
|
||||
},
|
||||
/// The next cycle begins.
|
||||
CycleStarted { index: CycleIndexOf<T> },
|
||||
/// A member swapped their account.
|
||||
Swapped { who: T::AccountId, new_who: T::AccountId },
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T, I = ()> {
|
||||
/// The salary system has already been started.
|
||||
AlreadyStarted,
|
||||
/// The account is not a ranked member.
|
||||
NotMember,
|
||||
/// The account is already inducted.
|
||||
AlreadyInducted,
|
||||
// The account is not yet inducted into the system.
|
||||
NotInducted,
|
||||
/// The member does not have a current valid claim.
|
||||
NoClaim,
|
||||
/// The member's claim is zero.
|
||||
ClaimZero,
|
||||
/// Current cycle's registration period is over.
|
||||
TooLate,
|
||||
/// Current cycle's payment period is not yet begun.
|
||||
TooEarly,
|
||||
/// Cycle is not yet over.
|
||||
NotYet,
|
||||
/// The payout cycles have not yet started.
|
||||
NotStarted,
|
||||
/// There is no budget left for the payout.
|
||||
Bankrupt,
|
||||
/// There was some issue with the mechanism of payment.
|
||||
PayError,
|
||||
/// The payment has neither failed nor succeeded yet.
|
||||
Inconclusive,
|
||||
/// The cycle is after that in which the payment was made.
|
||||
NotCurrent,
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
/// Start the first payout cycle.
|
||||
///
|
||||
/// - `origin`: A `Signed` origin of an account.
|
||||
#[pallet::weight(T::WeightInfo::init())]
|
||||
#[pallet::call_index(0)]
|
||||
pub fn init(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
|
||||
ensure_signed(origin)?;
|
||||
let now = pezframe_system::Pallet::<T>::block_number();
|
||||
ensure!(!Status::<T, I>::exists(), Error::<T, I>::AlreadyStarted);
|
||||
let status = StatusType {
|
||||
cycle_index: Zero::zero(),
|
||||
cycle_start: now,
|
||||
budget: T::Budget::get(),
|
||||
total_registrations: Zero::zero(),
|
||||
total_unregistered_paid: Zero::zero(),
|
||||
};
|
||||
Status::<T, I>::put(&status);
|
||||
|
||||
Self::deposit_event(Event::<T, I>::CycleStarted { index: status.cycle_index });
|
||||
Ok(Pays::No.into())
|
||||
}
|
||||
|
||||
/// Move to next payout cycle, assuming that the present block is now within that cycle.
|
||||
///
|
||||
/// - `origin`: A `Signed` origin of an account.
|
||||
#[pallet::weight(T::WeightInfo::bump())]
|
||||
#[pallet::call_index(1)]
|
||||
pub fn bump(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
|
||||
ensure_signed(origin)?;
|
||||
let now = pezframe_system::Pallet::<T>::block_number();
|
||||
let cycle_period = Self::cycle_period();
|
||||
let mut status = Status::<T, I>::get().ok_or(Error::<T, I>::NotStarted)?;
|
||||
status.cycle_start.saturating_accrue(cycle_period);
|
||||
ensure!(now >= status.cycle_start, Error::<T, I>::NotYet);
|
||||
status.cycle_index.saturating_inc();
|
||||
status.budget = T::Budget::get();
|
||||
status.total_registrations = Zero::zero();
|
||||
status.total_unregistered_paid = Zero::zero();
|
||||
Status::<T, I>::put(&status);
|
||||
|
||||
Self::deposit_event(Event::<T, I>::CycleStarted { index: status.cycle_index });
|
||||
Ok(Pays::No.into())
|
||||
}
|
||||
|
||||
/// Induct oneself into the payout system.
|
||||
#[pallet::weight(T::WeightInfo::induct())]
|
||||
#[pallet::call_index(2)]
|
||||
pub fn induct(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
|
||||
let who = ensure_signed(origin)?;
|
||||
let cycle_index = Status::<T, I>::get().ok_or(Error::<T, I>::NotStarted)?.cycle_index;
|
||||
T::Members::rank_of(&who).ok_or(Error::<T, I>::NotMember)?;
|
||||
ensure!(!Claimant::<T, I>::contains_key(&who), Error::<T, I>::AlreadyInducted);
|
||||
|
||||
Claimant::<T, I>::insert(
|
||||
&who,
|
||||
ClaimantStatus { last_active: cycle_index, status: Nothing },
|
||||
);
|
||||
|
||||
Self::deposit_event(Event::<T, I>::Inducted { who });
|
||||
Ok(Pays::No.into())
|
||||
}
|
||||
|
||||
/// Register for a payout.
|
||||
///
|
||||
/// Will only work if we are in the first `RegistrationPeriod` blocks since the cycle
|
||||
/// started.
|
||||
///
|
||||
/// - `origin`: A `Signed` origin of an account which is a member of `Members`.
|
||||
#[pallet::weight(T::WeightInfo::register())]
|
||||
#[pallet::call_index(3)]
|
||||
pub fn register(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
|
||||
let who = ensure_signed(origin)?;
|
||||
let rank = T::Members::rank_of(&who).ok_or(Error::<T, I>::NotMember)?;
|
||||
let mut status = Status::<T, I>::get().ok_or(Error::<T, I>::NotStarted)?;
|
||||
let mut claimant = Claimant::<T, I>::get(&who).ok_or(Error::<T, I>::NotInducted)?;
|
||||
let now = pezframe_system::Pallet::<T>::block_number();
|
||||
ensure!(
|
||||
now < status.cycle_start + T::RegistrationPeriod::get(),
|
||||
Error::<T, I>::TooLate
|
||||
);
|
||||
ensure!(claimant.last_active < status.cycle_index, Error::<T, I>::NoClaim);
|
||||
let payout = T::Salary::get_salary(rank, &who);
|
||||
ensure!(!payout.is_zero(), Error::<T, I>::ClaimZero);
|
||||
claimant.last_active = status.cycle_index;
|
||||
claimant.status = Registered(payout);
|
||||
status.total_registrations.saturating_accrue(payout);
|
||||
|
||||
Claimant::<T, I>::insert(&who, &claimant);
|
||||
Status::<T, I>::put(&status);
|
||||
|
||||
Self::deposit_event(Event::<T, I>::Registered { who, amount: payout });
|
||||
Ok(Pays::No.into())
|
||||
}
|
||||
|
||||
/// Request a payout.
|
||||
///
|
||||
/// Will only work if we are after the first `RegistrationPeriod` blocks since the cycle
|
||||
/// started but by no more than `PayoutPeriod` blocks.
|
||||
///
|
||||
/// - `origin`: A `Signed` origin of an account which is a member of `Members`.
|
||||
#[pallet::weight(T::WeightInfo::payout())]
|
||||
#[pallet::call_index(4)]
|
||||
pub fn payout(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
|
||||
let who = ensure_signed(origin)?;
|
||||
Self::do_payout(who.clone(), who)?;
|
||||
Ok(Pays::No.into())
|
||||
}
|
||||
|
||||
/// Request a payout to a secondary account.
|
||||
///
|
||||
/// Will only work if we are after the first `RegistrationPeriod` blocks since the cycle
|
||||
/// started but by no more than `PayoutPeriod` blocks.
|
||||
///
|
||||
/// - `origin`: A `Signed` origin of an account which is a member of `Members`.
|
||||
/// - `beneficiary`: The account to receive payment.
|
||||
#[pallet::weight(T::WeightInfo::payout_other())]
|
||||
#[pallet::call_index(5)]
|
||||
pub fn payout_other(
|
||||
origin: OriginFor<T>,
|
||||
beneficiary: T::AccountId,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
let who = ensure_signed(origin)?;
|
||||
Self::do_payout(who, beneficiary)?;
|
||||
Ok(Pays::No.into())
|
||||
}
|
||||
|
||||
/// Update a payment's status; if it failed, alter the state so the payment can be retried.
|
||||
///
|
||||
/// This must be called within the same cycle as the failed payment. It will fail with
|
||||
/// `Event::NotCurrent` otherwise.
|
||||
///
|
||||
/// - `origin`: A `Signed` origin of an account which is a member of `Members` who has
|
||||
/// received a payment this cycle.
|
||||
#[pallet::weight(T::WeightInfo::check_payment())]
|
||||
#[pallet::call_index(6)]
|
||||
pub fn check_payment(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
|
||||
let who = ensure_signed(origin)?;
|
||||
|
||||
let mut status = Status::<T, I>::get().ok_or(Error::<T, I>::NotStarted)?;
|
||||
let mut claimant = Claimant::<T, I>::get(&who).ok_or(Error::<T, I>::NotInducted)?;
|
||||
ensure!(claimant.last_active == status.cycle_index, Error::<T, I>::NotCurrent);
|
||||
let (id, registered, amount) = match claimant.status {
|
||||
Attempted { id, registered, amount } => (id, registered, amount),
|
||||
_ => return Err(Error::<T, I>::NoClaim.into()),
|
||||
};
|
||||
match T::Paymaster::check_payment(id) {
|
||||
PaymentStatus::Failure => {
|
||||
// Payment failed: we reset back to the status prior to payment.
|
||||
if let Some(amount) = registered {
|
||||
// Account registered; this makes it simple to roll back and allow retry.
|
||||
claimant.status = ClaimState::Registered(amount);
|
||||
} else {
|
||||
// Account didn't register; we set it to `Nothing` but must decrement
|
||||
// the `last_active` also to ensure a retry works.
|
||||
claimant.last_active.saturating_reduce(1u32.into());
|
||||
claimant.status = ClaimState::Nothing;
|
||||
// Since it is not registered, we must walk back our counter for what has
|
||||
// been paid.
|
||||
status.total_unregistered_paid.saturating_reduce(amount);
|
||||
}
|
||||
},
|
||||
PaymentStatus::Success => claimant.status = ClaimState::Nothing,
|
||||
_ => return Err(Error::<T, I>::Inconclusive.into()),
|
||||
}
|
||||
Claimant::<T, I>::insert(&who, &claimant);
|
||||
Status::<T, I>::put(&status);
|
||||
|
||||
Ok(Pays::No.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
pub fn status() -> Option<StatusOf<T, I>> {
|
||||
Status::<T, I>::get()
|
||||
}
|
||||
pub fn last_active(who: &T::AccountId) -> Result<CycleIndexOf<T>, DispatchError> {
|
||||
Ok(Claimant::<T, I>::get(&who).ok_or(Error::<T, I>::NotInducted)?.last_active)
|
||||
}
|
||||
pub fn cycle_period() -> BlockNumberFor<T> {
|
||||
T::RegistrationPeriod::get() + T::PayoutPeriod::get()
|
||||
}
|
||||
fn do_payout(who: T::AccountId, beneficiary: T::AccountId) -> DispatchResult {
|
||||
let mut status = Status::<T, I>::get().ok_or(Error::<T, I>::NotStarted)?;
|
||||
let mut claimant = Claimant::<T, I>::get(&who).ok_or(Error::<T, I>::NotInducted)?;
|
||||
|
||||
let now = pezframe_system::Pallet::<T>::block_number();
|
||||
ensure!(
|
||||
now >= status.cycle_start + T::RegistrationPeriod::get(),
|
||||
Error::<T, I>::TooEarly,
|
||||
);
|
||||
|
||||
let (payout, registered) = match claimant.status {
|
||||
Registered(unpaid) if claimant.last_active == status.cycle_index => {
|
||||
// Registered for this cycle. Pay accordingly.
|
||||
let payout = if status.total_registrations <= status.budget {
|
||||
// Can pay in full.
|
||||
unpaid
|
||||
} else {
|
||||
// Must be reduced pro-rata
|
||||
Perbill::from_rational(status.budget, status.total_registrations)
|
||||
.mul_floor(unpaid)
|
||||
};
|
||||
(payout, Some(unpaid))
|
||||
},
|
||||
Nothing | Attempted { .. } | Registered(_)
|
||||
if claimant.last_active < status.cycle_index =>
|
||||
{
|
||||
// Not registered for this cycle (or stale registration from previous cycle).
|
||||
// Pay from whatever is left.
|
||||
let rank = T::Members::rank_of(&who).ok_or(Error::<T, I>::NotMember)?;
|
||||
let ideal_payout = T::Salary::get_salary(rank, &who);
|
||||
|
||||
let pot = status
|
||||
.budget
|
||||
.saturating_sub(status.total_registrations)
|
||||
.saturating_sub(status.total_unregistered_paid);
|
||||
|
||||
let payout = ideal_payout.min(pot);
|
||||
ensure!(!payout.is_zero(), Error::<T, I>::ClaimZero);
|
||||
|
||||
status.total_unregistered_paid.saturating_accrue(payout);
|
||||
(payout, None)
|
||||
},
|
||||
_ => return Err(Error::<T, I>::NoClaim.into()),
|
||||
};
|
||||
|
||||
claimant.last_active = status.cycle_index;
|
||||
|
||||
let id =
|
||||
T::Paymaster::pay(&beneficiary, (), payout).map_err(|_| Error::<T, I>::PayError)?;
|
||||
|
||||
claimant.status = Attempted { registered, id, amount: payout };
|
||||
|
||||
Claimant::<T, I>::insert(&who, &claimant);
|
||||
Status::<T, I>::put(&status);
|
||||
|
||||
Self::deposit_event(Event::<T, I>::Paid { who, beneficiary, amount: payout, id });
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static>
|
||||
RankedMembersSwapHandler<T::AccountId, <T::Members as RankedMembers>::Rank> for Pallet<T, I>
|
||||
{
|
||||
fn swapped(
|
||||
who: &T::AccountId,
|
||||
new_who: &T::AccountId,
|
||||
_rank: <T::Members as RankedMembers>::Rank,
|
||||
) {
|
||||
if who == new_who {
|
||||
defensive!("Should not try to swap with self");
|
||||
return;
|
||||
}
|
||||
if Claimant::<T, I>::contains_key(new_who) {
|
||||
defensive!("Should not try to overwrite existing claimant");
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(claimant) = Claimant::<T, I>::take(who) else {
|
||||
defensive!("Claimant should exist when swapping");
|
||||
return;
|
||||
};
|
||||
|
||||
Claimant::<T, I>::insert(new_who, claimant);
|
||||
Self::deposit_event(Event::<T, I>::Swapped { who: who.clone(), new_who: new_who.clone() });
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
impl<T: Config<I>, I: 'static>
|
||||
pezpallet_ranked_collective::BenchmarkSetup<<T as pezframe_system::Config>::AccountId> for Pallet<T, I>
|
||||
{
|
||||
fn ensure_member(who: &<T as pezframe_system::Config>::AccountId) {
|
||||
Self::init(pezframe_system::RawOrigin::Signed(who.clone()).into()).unwrap();
|
||||
Self::induct(pezframe_system::RawOrigin::Signed(who.clone()).into()).unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
// 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.
|
||||
|
||||
//! The crate's tests.
|
||||
|
||||
use crate as pezpallet_salary;
|
||||
use crate::*;
|
||||
use frame::{deps::pezsp_io, testing_prelude::*};
|
||||
use pezpallet_ranked_collective::{EnsureRanked, Geometric};
|
||||
|
||||
type Rank = u16;
|
||||
type Block = pezframe_system::mocking::MockBlock<Test>;
|
||||
|
||||
construct_runtime!(
|
||||
pub struct Test {
|
||||
System: pezframe_system,
|
||||
Salary: pezpallet_salary,
|
||||
Club: pezpallet_ranked_collective,
|
||||
}
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
pub BlockWeights: pezframe_system::limits::BlockWeights =
|
||||
pezframe_system::limits::BlockWeights::simple_max(Weight::from_parts(1_000_000, 0));
|
||||
}
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Test {
|
||||
type Block = Block;
|
||||
}
|
||||
|
||||
pub struct MinRankOfClass<Delta>(PhantomData<Delta>);
|
||||
impl<Delta: Get<Rank>> Convert<u16, Rank> for MinRankOfClass<Delta> {
|
||||
fn convert(a: u16) -> Rank {
|
||||
a.saturating_sub(Delta::get())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestPay;
|
||||
impl Pay for TestPay {
|
||||
type Beneficiary = u64;
|
||||
type Balance = u64;
|
||||
type Id = u64;
|
||||
type AssetKind = ();
|
||||
type Error = ();
|
||||
|
||||
fn pay(
|
||||
_: &Self::Beneficiary,
|
||||
_: Self::AssetKind,
|
||||
_: Self::Balance,
|
||||
) -> Result<Self::Id, Self::Error> {
|
||||
unreachable!()
|
||||
}
|
||||
fn check_payment(_: Self::Id) -> PaymentStatus {
|
||||
unreachable!()
|
||||
}
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn ensure_successful(_: &Self::Beneficiary, _: Self::AssetKind, _: Self::Balance) {}
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn ensure_concluded(_: Self::Id) {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static Budget: u64 = 10;
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
type WeightInfo = ();
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Paymaster = TestPay;
|
||||
type Members = Club;
|
||||
type Salary = FixedSalary;
|
||||
type RegistrationPeriod = ConstU64<2>;
|
||||
type PayoutPeriod = ConstU64<2>;
|
||||
type Budget = Budget;
|
||||
}
|
||||
|
||||
pub struct FixedSalary;
|
||||
impl GetSalary<u16, u64, u64> for FixedSalary {
|
||||
fn get_salary(_rank: u16, _who: &u64) -> u64 {
|
||||
123
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static MinRankOfClassDelta: Rank = 0;
|
||||
}
|
||||
|
||||
impl pezpallet_ranked_collective::Config for Test {
|
||||
type WeightInfo = ();
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type PromoteOrigin = EitherOf<
|
||||
// Root can promote arbitrarily.
|
||||
pezframe_system::EnsureRootWithSuccess<Self::AccountId, ConstU16<65535>>,
|
||||
// Members can promote up to the rank of 2 below them.
|
||||
MapSuccess<EnsureRanked<Test, (), 2>, ReduceBy<ConstU16<2>>>,
|
||||
>;
|
||||
type AddOrigin = MapSuccess<Self::PromoteOrigin, ReplaceWithDefault<()>>;
|
||||
type DemoteOrigin = EitherOf<
|
||||
// Root can demote arbitrarily.
|
||||
pezframe_system::EnsureRootWithSuccess<Self::AccountId, ConstU16<65535>>,
|
||||
// Members can demote up to the rank of 3 below them.
|
||||
MapSuccess<EnsureRanked<Test, (), 3>, ReduceBy<ConstU16<3>>>,
|
||||
>;
|
||||
type RemoveOrigin = Self::DemoteOrigin;
|
||||
type ExchangeOrigin = EitherOf<
|
||||
// Root can exchange arbitrarily.
|
||||
pezframe_system::EnsureRootWithSuccess<Self::AccountId, ConstU16<65535>>,
|
||||
// Members can exchange up to the rank of 2 below them.
|
||||
MapSuccess<EnsureRanked<Test, (), 2>, ReduceBy<ConstU16<2>>>,
|
||||
>;
|
||||
type Polls = NoOpPoll<BlockNumberFor<Test>>;
|
||||
type MinRankOfClass = MinRankOfClass<MinRankOfClassDelta>;
|
||||
type MemberSwappedHandler = Salary;
|
||||
type VoteWeight = Geometric;
|
||||
type MaxMemberCount = ();
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkSetup = Salary;
|
||||
}
|
||||
|
||||
pub fn new_test_ext() -> TestState {
|
||||
let t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
let mut ext = TestState::new(t);
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
ext
|
||||
}
|
||||
|
||||
fn assert_last_event(generic_event: <Test as Config>::RuntimeEvent) {
|
||||
let events = pezframe_system::Pallet::<Test>::events();
|
||||
let system_event: <Test as pezframe_system::Config>::RuntimeEvent = generic_event.into();
|
||||
let pezframe_system::EventRecord { event, .. } = events.last().expect("Event expected");
|
||||
assert_eq!(event, &system_event.into());
|
||||
}
|
||||
|
||||
fn promote_n_times(acc: u64, r: u16) {
|
||||
for _ in 0..r {
|
||||
assert_ok!(Club::promote_member(RuntimeOrigin::root(), acc));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn swap_simple_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
for i in 0u16..9 {
|
||||
let acc = i as u64;
|
||||
|
||||
assert_ok!(Club::add_member(RuntimeOrigin::root(), acc));
|
||||
promote_n_times(acc, i);
|
||||
let _ = Salary::init(RuntimeOrigin::signed(acc));
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(acc)));
|
||||
|
||||
// Swapping normally works:
|
||||
assert_ok!(Club::exchange_member(RuntimeOrigin::root(), acc, acc + 10));
|
||||
assert_last_event(Event::Swapped { who: acc, new_who: acc + 10 }.into());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn swap_exhaustive_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let root_add = hypothetically!({
|
||||
assert_ok!(Club::add_member(RuntimeOrigin::root(), 1));
|
||||
assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1));
|
||||
assert_ok!(Salary::init(RuntimeOrigin::signed(1)));
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(1)));
|
||||
|
||||
// The events mess up the storage root:
|
||||
System::reset_events();
|
||||
pezsp_io::storage::root(StateVersion::V1)
|
||||
});
|
||||
|
||||
let root_swap = hypothetically!({
|
||||
assert_ok!(Club::add_member(RuntimeOrigin::root(), 0));
|
||||
assert_ok!(Club::promote_member(RuntimeOrigin::root(), 0));
|
||||
assert_ok!(Salary::init(RuntimeOrigin::signed(0)));
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(0)));
|
||||
|
||||
assert_ok!(Club::exchange_member(RuntimeOrigin::root(), 0, 1));
|
||||
|
||||
// The events mess up the storage root:
|
||||
System::reset_events();
|
||||
pezsp_io::storage::root(StateVersion::V1)
|
||||
});
|
||||
|
||||
assert_eq!(root_add, root_swap);
|
||||
// Ensure that we don't compare trivial stuff like `()` from a type error above.
|
||||
assert_eq!(root_add.len(), 32);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn swap_bad_noops() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Club::add_member(RuntimeOrigin::root(), 0));
|
||||
promote_n_times(0, 0);
|
||||
assert_ok!(Salary::init(RuntimeOrigin::signed(0)));
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(0)));
|
||||
assert_ok!(Club::add_member(RuntimeOrigin::root(), 1));
|
||||
promote_n_times(1, 1);
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(1)));
|
||||
|
||||
// Swapping for another member is a noop:
|
||||
assert_noop!(
|
||||
Club::exchange_member(RuntimeOrigin::root(), 0, 1),
|
||||
pezpallet_ranked_collective::Error::<Test>::AlreadyMember
|
||||
);
|
||||
// Swapping for the same member is a noop:
|
||||
assert_noop!(
|
||||
Club::exchange_member(RuntimeOrigin::root(), 0, 0),
|
||||
pezpallet_ranked_collective::Error::<Test>::SameMember
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// 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.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
//! Unit and integration tests for the salary pallet.
|
||||
|
||||
pub(crate) mod integration;
|
||||
pub(crate) mod unit;
|
||||
@@ -0,0 +1,637 @@
|
||||
// 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.
|
||||
|
||||
//! The crate's tests.
|
||||
|
||||
use crate as pezpallet_salary;
|
||||
use crate::*;
|
||||
use core::cell::RefCell;
|
||||
use frame::{deps::pezsp_runtime::traits::Identity, testing_prelude::*, traits::tokens::ConvertRank};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
type Block = MockBlock<Test>;
|
||||
|
||||
construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: pezframe_system,
|
||||
Salary: pezpallet_salary,
|
||||
}
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
pub BlockWeights: pezframe_system::limits::BlockWeights =
|
||||
pezframe_system::limits::BlockWeights::simple_max(Weight::from_parts(1_000_000, 0));
|
||||
}
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Test {
|
||||
type Block = Block;
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
pub static PAID: RefCell<BTreeMap<u64, u64>> = RefCell::new(BTreeMap::new());
|
||||
pub static STATUS: RefCell<BTreeMap<u64, PaymentStatus>> = RefCell::new(BTreeMap::new());
|
||||
pub static LAST_ID: RefCell<u64> = RefCell::new(0u64);
|
||||
}
|
||||
|
||||
fn paid(who: u64) -> u64 {
|
||||
PAID.with(|p| p.borrow().get(&who).cloned().unwrap_or(0))
|
||||
}
|
||||
fn unpay(who: u64, amount: u64) {
|
||||
PAID.with(|p| p.borrow_mut().entry(who).or_default().saturating_reduce(amount))
|
||||
}
|
||||
fn set_status(id: u64, s: PaymentStatus) {
|
||||
STATUS.with(|m| m.borrow_mut().insert(id, s));
|
||||
}
|
||||
|
||||
pub struct TestPay;
|
||||
impl Pay for TestPay {
|
||||
type Beneficiary = u64;
|
||||
type Balance = u64;
|
||||
type Id = u64;
|
||||
type AssetKind = ();
|
||||
type Error = ();
|
||||
|
||||
fn pay(
|
||||
who: &Self::Beneficiary,
|
||||
_: Self::AssetKind,
|
||||
amount: Self::Balance,
|
||||
) -> Result<Self::Id, Self::Error> {
|
||||
PAID.with(|paid| *paid.borrow_mut().entry(*who).or_default() += amount);
|
||||
Ok(LAST_ID.with(|lid| {
|
||||
let x = *lid.borrow();
|
||||
lid.replace(x + 1);
|
||||
x
|
||||
}))
|
||||
}
|
||||
fn check_payment(id: Self::Id) -> PaymentStatus {
|
||||
STATUS.with(|s| s.borrow().get(&id).cloned().unwrap_or(PaymentStatus::Unknown))
|
||||
}
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn ensure_successful(_: &Self::Beneficiary, _: Self::AssetKind, _: Self::Balance) {}
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn ensure_concluded(id: Self::Id) {
|
||||
set_status(id, PaymentStatus::Failure)
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
pub static CLUB: RefCell<BTreeMap<u64, u64>> = RefCell::new(BTreeMap::new());
|
||||
}
|
||||
|
||||
pub struct TestClub;
|
||||
impl RankedMembers for TestClub {
|
||||
type AccountId = u64;
|
||||
type Rank = u64;
|
||||
fn min_rank() -> Self::Rank {
|
||||
0
|
||||
}
|
||||
fn rank_of(who: &Self::AccountId) -> Option<Self::Rank> {
|
||||
CLUB.with(|club| club.borrow().get(who).cloned())
|
||||
}
|
||||
fn induct(who: &Self::AccountId) -> DispatchResult {
|
||||
CLUB.with(|club| club.borrow_mut().insert(*who, 0));
|
||||
Ok(())
|
||||
}
|
||||
fn promote(who: &Self::AccountId) -> DispatchResult {
|
||||
CLUB.with(|club| {
|
||||
club.borrow_mut().entry(*who).and_modify(|r| *r += 1);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
fn demote(who: &Self::AccountId) -> DispatchResult {
|
||||
CLUB.with(|club| match club.borrow().get(who) {
|
||||
None => Err(DispatchError::Unavailable),
|
||||
Some(&0) => {
|
||||
club.borrow_mut().remove(&who);
|
||||
Ok(())
|
||||
},
|
||||
Some(_) => {
|
||||
club.borrow_mut().entry(*who).and_modify(|x| *x -= 1);
|
||||
Ok(())
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn set_rank(who: u64, rank: u64) {
|
||||
CLUB.with(|club| club.borrow_mut().insert(who, rank));
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static Budget: u64 = 10;
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
type WeightInfo = ();
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Paymaster = TestPay;
|
||||
type Members = TestClub;
|
||||
type Salary = ConvertRank<Identity>;
|
||||
type RegistrationPeriod = ConstU64<2>;
|
||||
type PayoutPeriod = ConstU64<2>;
|
||||
type Budget = Budget;
|
||||
}
|
||||
|
||||
pub fn new_test_ext() -> TestState {
|
||||
let t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
let mut ext = TestState::new(t);
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
ext
|
||||
}
|
||||
|
||||
fn next_block() {
|
||||
System::set_block_number(System::block_number() + 1);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn run_to(n: u64) {
|
||||
while System::block_number() < n {
|
||||
next_block();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn basic_stuff() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert!(Salary::last_active(&0).is_err());
|
||||
assert_eq!(Salary::status(), None);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_start() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Salary::init(RuntimeOrigin::signed(1)));
|
||||
assert_eq!(
|
||||
Salary::status(),
|
||||
Some(StatusType {
|
||||
cycle_index: 0,
|
||||
cycle_start: 1,
|
||||
budget: 10,
|
||||
total_registrations: 0,
|
||||
total_unregistered_paid: 0,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bump_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Salary::init(RuntimeOrigin::signed(1)));
|
||||
run_to(4);
|
||||
assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::<Test>::NotYet);
|
||||
|
||||
run_to(5);
|
||||
assert_ok!(Salary::bump(RuntimeOrigin::signed(1)));
|
||||
assert_eq!(
|
||||
Salary::status(),
|
||||
Some(StatusType {
|
||||
cycle_index: 1,
|
||||
cycle_start: 5,
|
||||
budget: 10,
|
||||
total_registrations: 0,
|
||||
total_unregistered_paid: 0
|
||||
})
|
||||
);
|
||||
|
||||
run_to(8);
|
||||
assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::<Test>::NotYet);
|
||||
|
||||
BUDGET.with(|b| b.replace(5));
|
||||
run_to(9);
|
||||
assert_ok!(Salary::bump(RuntimeOrigin::signed(1)));
|
||||
assert_eq!(
|
||||
Salary::status(),
|
||||
Some(StatusType {
|
||||
cycle_index: 2,
|
||||
cycle_start: 9,
|
||||
budget: 5,
|
||||
total_registrations: 0,
|
||||
total_unregistered_paid: 0
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn induct_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Salary::init(RuntimeOrigin::signed(1)));
|
||||
|
||||
assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::<Test>::NotMember);
|
||||
set_rank(1, 1);
|
||||
assert!(Salary::last_active(&1).is_err());
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(1)));
|
||||
assert_eq!(Salary::last_active(&1).unwrap(), 0);
|
||||
assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::<Test>::AlreadyInducted);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unregistered_payment_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
set_rank(1, 1);
|
||||
assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::<Test>::NotStarted);
|
||||
assert_ok!(Salary::init(RuntimeOrigin::signed(1)));
|
||||
assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::<Test>::NotInducted);
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(1)));
|
||||
// No claim on the cycle active during induction.
|
||||
assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::<Test>::TooEarly);
|
||||
run_to(3);
|
||||
assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::<Test>::NoClaim);
|
||||
|
||||
run_to(6);
|
||||
assert_ok!(Salary::bump(RuntimeOrigin::signed(1)));
|
||||
assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::<Test>::TooEarly);
|
||||
run_to(7);
|
||||
assert_ok!(Salary::payout(RuntimeOrigin::signed(1)));
|
||||
assert_eq!(paid(1), 1);
|
||||
assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1);
|
||||
assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::<Test>::NoClaim);
|
||||
run_to(8);
|
||||
assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::<Test>::NotYet);
|
||||
run_to(9);
|
||||
assert_ok!(Salary::bump(RuntimeOrigin::signed(1)));
|
||||
run_to(11);
|
||||
assert_ok!(Salary::payout_other(RuntimeOrigin::signed(1), 10));
|
||||
assert_eq!(paid(1), 1);
|
||||
assert_eq!(paid(10), 1);
|
||||
assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retry_payment_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
set_rank(1, 1);
|
||||
assert_ok!(Salary::init(RuntimeOrigin::signed(1)));
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(1)));
|
||||
run_to(6);
|
||||
assert_ok!(Salary::bump(RuntimeOrigin::signed(1)));
|
||||
run_to(7);
|
||||
assert_ok!(Salary::payout(RuntimeOrigin::signed(1)));
|
||||
|
||||
// Payment failed.
|
||||
unpay(1, 1);
|
||||
set_status(0, PaymentStatus::Failure);
|
||||
|
||||
assert_eq!(paid(1), 0);
|
||||
assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1);
|
||||
|
||||
// Can't just retry.
|
||||
assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::<Test>::NoClaim);
|
||||
// Check status.
|
||||
assert_ok!(Salary::check_payment(RuntimeOrigin::signed(1)));
|
||||
// Allowed to try again.
|
||||
assert_ok!(Salary::payout(RuntimeOrigin::signed(1)));
|
||||
|
||||
assert_eq!(paid(1), 1);
|
||||
assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1);
|
||||
|
||||
assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::<Test>::NoClaim);
|
||||
run_to(8);
|
||||
assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::<Test>::NotYet);
|
||||
run_to(9);
|
||||
assert_ok!(Salary::bump(RuntimeOrigin::signed(1)));
|
||||
run_to(11);
|
||||
assert_ok!(Salary::payout_other(RuntimeOrigin::signed(1), 10));
|
||||
assert_eq!(paid(1), 1);
|
||||
assert_eq!(paid(10), 1);
|
||||
assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retry_registered_payment_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
set_rank(1, 1);
|
||||
assert_ok!(Salary::init(RuntimeOrigin::signed(1)));
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(1)));
|
||||
run_to(6);
|
||||
assert_ok!(Salary::bump(RuntimeOrigin::signed(1)));
|
||||
assert_ok!(Salary::register(RuntimeOrigin::signed(1)));
|
||||
run_to(7);
|
||||
assert_ok!(Salary::payout(RuntimeOrigin::signed(1)));
|
||||
|
||||
// Payment failed.
|
||||
unpay(1, 1);
|
||||
set_status(0, PaymentStatus::Failure);
|
||||
|
||||
assert_eq!(paid(1), 0);
|
||||
assert_eq!(Salary::status().unwrap().total_unregistered_paid, 0);
|
||||
|
||||
assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::<Test>::NoClaim);
|
||||
// Check status.
|
||||
assert_ok!(Salary::check_payment(RuntimeOrigin::signed(1)));
|
||||
// Allowed to try again.
|
||||
assert_ok!(Salary::payout(RuntimeOrigin::signed(1)));
|
||||
|
||||
assert_eq!(paid(1), 1);
|
||||
assert_eq!(Salary::status().unwrap().total_unregistered_paid, 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retry_payment_later_is_not_allowed() {
|
||||
new_test_ext().execute_with(|| {
|
||||
set_rank(1, 1);
|
||||
assert_ok!(Salary::init(RuntimeOrigin::signed(1)));
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(1)));
|
||||
run_to(6);
|
||||
assert_ok!(Salary::bump(RuntimeOrigin::signed(1)));
|
||||
run_to(7);
|
||||
assert_ok!(Salary::payout(RuntimeOrigin::signed(1)));
|
||||
|
||||
// Payment failed.
|
||||
unpay(1, 1);
|
||||
set_status(0, PaymentStatus::Failure);
|
||||
|
||||
assert_eq!(paid(1), 0);
|
||||
assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1);
|
||||
|
||||
// Can't just retry.
|
||||
assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::<Test>::NoClaim);
|
||||
|
||||
// Next cycle.
|
||||
run_to(9);
|
||||
assert_ok!(Salary::bump(RuntimeOrigin::signed(1)));
|
||||
|
||||
// Payment did fail but now too late to retry.
|
||||
assert_noop!(Salary::check_payment(RuntimeOrigin::signed(1)), Error::<Test>::NotCurrent);
|
||||
|
||||
// We do get this cycle's payout, but we must wait for the payout period to start.
|
||||
assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::<Test>::TooEarly);
|
||||
|
||||
run_to(11);
|
||||
assert_ok!(Salary::payout(RuntimeOrigin::signed(1)));
|
||||
assert_eq!(paid(1), 1);
|
||||
assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1);
|
||||
assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::<Test>::NoClaim);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retry_payment_later_without_bump_is_allowed() {
|
||||
new_test_ext().execute_with(|| {
|
||||
set_rank(1, 1);
|
||||
assert_ok!(Salary::init(RuntimeOrigin::signed(1)));
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(1)));
|
||||
run_to(6);
|
||||
assert_ok!(Salary::bump(RuntimeOrigin::signed(1)));
|
||||
run_to(7);
|
||||
assert_ok!(Salary::payout(RuntimeOrigin::signed(1)));
|
||||
|
||||
// Payment failed.
|
||||
unpay(1, 1);
|
||||
set_status(0, PaymentStatus::Failure);
|
||||
|
||||
// Next cycle.
|
||||
run_to(9);
|
||||
|
||||
// Payment did fail but we can still retry as long as we don't `bump`.
|
||||
assert_ok!(Salary::check_payment(RuntimeOrigin::signed(1)));
|
||||
assert_ok!(Salary::payout(RuntimeOrigin::signed(1)));
|
||||
|
||||
assert_eq!(paid(1), 1);
|
||||
assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retry_payment_to_other_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
set_rank(1, 1);
|
||||
assert_ok!(Salary::init(RuntimeOrigin::signed(1)));
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(1)));
|
||||
run_to(6);
|
||||
assert_ok!(Salary::bump(RuntimeOrigin::signed(1)));
|
||||
run_to(7);
|
||||
assert_ok!(Salary::payout_other(RuntimeOrigin::signed(1), 10));
|
||||
|
||||
// Payment failed.
|
||||
unpay(10, 1);
|
||||
set_status(0, PaymentStatus::Failure);
|
||||
|
||||
// Can't just retry.
|
||||
assert_noop!(Salary::payout_other(RuntimeOrigin::signed(1), 10), Error::<Test>::NoClaim);
|
||||
// Check status.
|
||||
assert_ok!(Salary::check_payment(RuntimeOrigin::signed(1)));
|
||||
// Allowed to try again.
|
||||
assert_ok!(Salary::payout_other(RuntimeOrigin::signed(1), 10));
|
||||
|
||||
assert_eq!(paid(10), 1);
|
||||
assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1);
|
||||
|
||||
assert_noop!(Salary::payout_other(RuntimeOrigin::signed(1), 10), Error::<Test>::NoClaim);
|
||||
run_to(8);
|
||||
assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::<Test>::NotYet);
|
||||
run_to(9);
|
||||
assert_ok!(Salary::bump(RuntimeOrigin::signed(1)));
|
||||
run_to(11);
|
||||
assert_ok!(Salary::payout(RuntimeOrigin::signed(1)));
|
||||
assert_eq!(paid(1), 1);
|
||||
assert_eq!(paid(10), 1);
|
||||
assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registered_payment_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
set_rank(1, 1);
|
||||
assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::<Test>::NotStarted);
|
||||
assert_ok!(Salary::init(RuntimeOrigin::signed(1)));
|
||||
assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::<Test>::NotInducted);
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(1)));
|
||||
// No claim on the cycle active during induction.
|
||||
assert_noop!(Salary::register(RuntimeOrigin::signed(1)), Error::<Test>::NoClaim);
|
||||
run_to(3);
|
||||
assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::<Test>::NoClaim);
|
||||
|
||||
run_to(5);
|
||||
assert_ok!(Salary::bump(RuntimeOrigin::signed(1)));
|
||||
assert_ok!(Salary::register(RuntimeOrigin::signed(1)));
|
||||
assert_eq!(Salary::status().unwrap().total_registrations, 1);
|
||||
run_to(7);
|
||||
assert_ok!(Salary::payout(RuntimeOrigin::signed(1)));
|
||||
assert_eq!(paid(1), 1);
|
||||
assert_eq!(Salary::status().unwrap().total_unregistered_paid, 0);
|
||||
assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::<Test>::NoClaim);
|
||||
|
||||
run_to(9);
|
||||
assert_ok!(Salary::bump(RuntimeOrigin::signed(1)));
|
||||
assert_eq!(Salary::status().unwrap().total_registrations, 0);
|
||||
assert_ok!(Salary::register(RuntimeOrigin::signed(1)));
|
||||
assert_eq!(Salary::status().unwrap().total_registrations, 1);
|
||||
run_to(11);
|
||||
assert_ok!(Salary::payout(RuntimeOrigin::signed(1)));
|
||||
assert_eq!(paid(1), 2);
|
||||
assert_eq!(Salary::status().unwrap().total_unregistered_paid, 0);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_payment_fails() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Salary::init(RuntimeOrigin::signed(1)));
|
||||
set_rank(1, 0);
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(1)));
|
||||
run_to(7);
|
||||
assert_ok!(Salary::bump(RuntimeOrigin::signed(1)));
|
||||
assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::<Test>::ClaimZero);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unregistered_bankruptcy_fails_gracefully() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Salary::init(RuntimeOrigin::signed(1)));
|
||||
set_rank(1, 2);
|
||||
set_rank(2, 6);
|
||||
set_rank(3, 12);
|
||||
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(1)));
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(2)));
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(3)));
|
||||
|
||||
run_to(7);
|
||||
assert_ok!(Salary::bump(RuntimeOrigin::signed(1)));
|
||||
assert_ok!(Salary::payout(RuntimeOrigin::signed(1)));
|
||||
assert_ok!(Salary::payout(RuntimeOrigin::signed(2)));
|
||||
assert_ok!(Salary::payout(RuntimeOrigin::signed(3)));
|
||||
|
||||
assert_eq!(paid(1), 2);
|
||||
assert_eq!(paid(2), 6);
|
||||
assert_eq!(paid(3), 2);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn registered_bankruptcy_fails_gracefully() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Salary::init(RuntimeOrigin::signed(1)));
|
||||
set_rank(1, 2);
|
||||
set_rank(2, 6);
|
||||
set_rank(3, 12);
|
||||
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(1)));
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(2)));
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(3)));
|
||||
|
||||
run_to(5);
|
||||
assert_ok!(Salary::bump(RuntimeOrigin::signed(1)));
|
||||
assert_ok!(Salary::register(RuntimeOrigin::signed(1)));
|
||||
assert_ok!(Salary::register(RuntimeOrigin::signed(2)));
|
||||
assert_ok!(Salary::register(RuntimeOrigin::signed(3)));
|
||||
|
||||
run_to(7);
|
||||
assert_ok!(Salary::payout(RuntimeOrigin::signed(1)));
|
||||
assert_ok!(Salary::payout(RuntimeOrigin::signed(2)));
|
||||
assert_ok!(Salary::payout(RuntimeOrigin::signed(3)));
|
||||
|
||||
assert_eq!(paid(1), 1);
|
||||
assert_eq!(paid(2), 3);
|
||||
assert_eq!(paid(3), 6);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_bankruptcy_fails_gracefully() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Salary::init(RuntimeOrigin::signed(1)));
|
||||
set_rank(1, 2);
|
||||
set_rank(2, 6);
|
||||
set_rank(3, 12);
|
||||
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(1)));
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(2)));
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(3)));
|
||||
|
||||
run_to(5);
|
||||
assert_ok!(Salary::bump(RuntimeOrigin::signed(1)));
|
||||
assert_ok!(Salary::register(RuntimeOrigin::signed(1)));
|
||||
assert_ok!(Salary::register(RuntimeOrigin::signed(2)));
|
||||
|
||||
run_to(7);
|
||||
assert_ok!(Salary::payout(RuntimeOrigin::signed(3)));
|
||||
assert_ok!(Salary::payout(RuntimeOrigin::signed(2)));
|
||||
assert_ok!(Salary::payout(RuntimeOrigin::signed(1)));
|
||||
|
||||
assert_eq!(paid(1), 2);
|
||||
assert_eq!(paid(2), 6);
|
||||
assert_eq!(paid(3), 2);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn other_mixed_bankruptcy_fails_gracefully() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Salary::init(RuntimeOrigin::signed(1)));
|
||||
set_rank(1, 2);
|
||||
set_rank(2, 6);
|
||||
set_rank(3, 12);
|
||||
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(1)));
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(2)));
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(3)));
|
||||
|
||||
run_to(5);
|
||||
assert_ok!(Salary::bump(RuntimeOrigin::signed(1)));
|
||||
assert_ok!(Salary::register(RuntimeOrigin::signed(2)));
|
||||
assert_ok!(Salary::register(RuntimeOrigin::signed(3)));
|
||||
|
||||
run_to(7);
|
||||
assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::<Test>::ClaimZero);
|
||||
assert_ok!(Salary::payout(RuntimeOrigin::signed(2)));
|
||||
assert_ok!(Salary::payout(RuntimeOrigin::signed(3)));
|
||||
|
||||
assert_eq!(paid(1), 0);
|
||||
assert_eq!(paid(2), 3);
|
||||
assert_eq!(paid(3), 6);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stale_registration_from_previous_cycle_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
set_rank(1, 1);
|
||||
assert_ok!(Salary::init(RuntimeOrigin::signed(1)));
|
||||
assert_ok!(Salary::induct(RuntimeOrigin::signed(1)));
|
||||
|
||||
run_to(5);
|
||||
assert_ok!(Salary::bump(RuntimeOrigin::signed(1)));
|
||||
assert_ok!(Salary::register(RuntimeOrigin::signed(1)));
|
||||
assert_eq!(Salary::status().unwrap().total_registrations, 1);
|
||||
|
||||
// Miss the payout window for cycle 1 (don't call payout)
|
||||
// Start cycle 2 without claiming from cycle 1
|
||||
run_to(9);
|
||||
assert_ok!(Salary::bump(RuntimeOrigin::signed(1)));
|
||||
assert_eq!(Salary::status().unwrap().cycle_index, 2);
|
||||
assert_eq!(Salary::status().unwrap().total_registrations, 0);
|
||||
|
||||
run_to(11);
|
||||
assert_ok!(Salary::payout(RuntimeOrigin::signed(1)));
|
||||
|
||||
// Should get paid from the unregistered pool
|
||||
assert_eq!(paid(1), 1);
|
||||
assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
// 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.
|
||||
|
||||
// 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.
|
||||
|
||||
//! Autogenerated weights for `pezpallet_salary`
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 32.0.0
|
||||
//! DATE: 2025-02-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `4563561839a5`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
|
||||
//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024`
|
||||
|
||||
// Executed Command:
|
||||
// frame-omni-bencher
|
||||
// v1
|
||||
// benchmark
|
||||
// pallet
|
||||
// --extrinsic=*
|
||||
// --runtime=target/production/wbuild/kitchensink-runtime/kitchensink_runtime.wasm
|
||||
// --pallet=pezpallet_salary
|
||||
// --header=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/HEADER-APACHE2
|
||||
// --output=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/pezframe/salary/src/weights.rs
|
||||
// --wasm-execution=compiled
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --heap-pages=4096
|
||||
// --template=bizinikiwi/.maintain/frame-umbrella-weight-template.hbs
|
||||
// --no-storage-info
|
||||
// --no-min-squares
|
||||
// --no-median-slopes
|
||||
// --genesis-builder-policy=none
|
||||
// --exclude-pallets=pezpallet_xcm,pezpallet_xcm_benchmarks::fungible,pezpallet_xcm_benchmarks::generic,pezpallet_nomination_pools,pezpallet_remark,pezpallet_transaction_storage,pezpallet_election_provider_multi_block,pezpallet_election_provider_multi_block::signed,pezpallet_election_provider_multi_block::unsigned,pezpallet_election_provider_multi_block::verifier
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(missing_docs)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use frame::weights_prelude::*;
|
||||
|
||||
/// Weight functions needed for `pezpallet_salary`.
|
||||
pub trait WeightInfo {
|
||||
fn init() -> Weight;
|
||||
fn bump() -> Weight;
|
||||
fn induct() -> Weight;
|
||||
fn register() -> Weight;
|
||||
fn payout() -> Weight;
|
||||
fn payout_other() -> Weight;
|
||||
fn check_payment() -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for `pezpallet_salary` using the Bizinikiwi node and recommended hardware.
|
||||
pub struct BizinikiwiWeight<T>(PhantomData<T>);
|
||||
impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
|
||||
/// Storage: `Salary::Status` (r:1 w:1)
|
||||
/// Proof: `Salary::Status` (`max_values`: Some(1), `max_size`: Some(56), added: 551, mode: `MaxEncodedLen`)
|
||||
fn init() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `1541`
|
||||
// Minimum execution time: 5_326_000 picoseconds.
|
||||
Weight::from_parts(5_563_000, 1541)
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `Salary::Status` (r:1 w:1)
|
||||
/// Proof: `Salary::Status` (`max_values`: Some(1), `max_size`: Some(56), added: 551, mode: `MaxEncodedLen`)
|
||||
fn bump() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `26`
|
||||
// Estimated: `1541`
|
||||
// Minimum execution time: 6_708_000 picoseconds.
|
||||
Weight::from_parts(6_971_000, 1541)
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `Salary::Status` (r:1 w:0)
|
||||
/// Proof: `Salary::Status` (`max_values`: Some(1), `max_size`: Some(56), added: 551, mode: `MaxEncodedLen`)
|
||||
/// Storage: `RankedCollective::Members` (r:1 w:0)
|
||||
/// Proof: `RankedCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Salary::Claimant` (r:1 w:1)
|
||||
/// Proof: `Salary::Claimant` (`max_values`: None, `max_size`: Some(78), added: 2553, mode: `MaxEncodedLen`)
|
||||
fn induct() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `177`
|
||||
// Estimated: `3543`
|
||||
// Minimum execution time: 14_418_000 picoseconds.
|
||||
Weight::from_parts(14_844_000, 3543)
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `RankedCollective::Members` (r:1 w:0)
|
||||
/// Proof: `RankedCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Salary::Status` (r:1 w:1)
|
||||
/// Proof: `Salary::Status` (`max_values`: Some(1), `max_size`: Some(56), added: 551, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Salary::Claimant` (r:1 w:1)
|
||||
/// Proof: `Salary::Claimant` (`max_values`: None, `max_size`: Some(78), added: 2553, mode: `MaxEncodedLen`)
|
||||
fn register() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `229`
|
||||
// Estimated: `3543`
|
||||
// Minimum execution time: 18_333_000 picoseconds.
|
||||
Weight::from_parts(18_876_000, 3543)
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: `Salary::Status` (r:1 w:1)
|
||||
/// Proof: `Salary::Status` (`max_values`: Some(1), `max_size`: Some(56), added: 551, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Salary::Claimant` (r:1 w:1)
|
||||
/// Proof: `Salary::Claimant` (`max_values`: None, `max_size`: Some(78), added: 2553, mode: `MaxEncodedLen`)
|
||||
/// Storage: `RankedCollective::Members` (r:1 w:0)
|
||||
/// Proof: `RankedCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`)
|
||||
fn payout() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `229`
|
||||
// Estimated: `3543`
|
||||
// Minimum execution time: 60_650_000 picoseconds.
|
||||
Weight::from_parts(61_965_000, 3543)
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: `Salary::Status` (r:1 w:1)
|
||||
/// Proof: `Salary::Status` (`max_values`: Some(1), `max_size`: Some(56), added: 551, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Salary::Claimant` (r:1 w:1)
|
||||
/// Proof: `Salary::Claimant` (`max_values`: None, `max_size`: Some(78), added: 2553, mode: `MaxEncodedLen`)
|
||||
/// Storage: `RankedCollective::Members` (r:1 w:0)
|
||||
/// Proof: `RankedCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
fn payout_other() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `229`
|
||||
// Estimated: `3593`
|
||||
// Minimum execution time: 60_860_000 picoseconds.
|
||||
Weight::from_parts(61_739_000, 3593)
|
||||
.saturating_add(T::DbWeight::get().reads(4_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: `Salary::Status` (r:1 w:1)
|
||||
/// Proof: `Salary::Status` (`max_values`: Some(1), `max_size`: Some(56), added: 551, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Salary::Claimant` (r:1 w:1)
|
||||
/// Proof: `Salary::Claimant` (`max_values`: None, `max_size`: Some(78), added: 2553, mode: `MaxEncodedLen`)
|
||||
fn check_payment() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `95`
|
||||
// Estimated: `3543`
|
||||
// Minimum execution time: 9_559_000 picoseconds.
|
||||
Weight::from_parts(9_936_000, 3543)
|
||||
.saturating_add(T::DbWeight::get().reads(2_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests.
|
||||
impl WeightInfo for () {
|
||||
/// Storage: `Salary::Status` (r:1 w:1)
|
||||
/// Proof: `Salary::Status` (`max_values`: Some(1), `max_size`: Some(56), added: 551, mode: `MaxEncodedLen`)
|
||||
fn init() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `1541`
|
||||
// Minimum execution time: 5_326_000 picoseconds.
|
||||
Weight::from_parts(5_563_000, 1541)
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `Salary::Status` (r:1 w:1)
|
||||
/// Proof: `Salary::Status` (`max_values`: Some(1), `max_size`: Some(56), added: 551, mode: `MaxEncodedLen`)
|
||||
fn bump() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `26`
|
||||
// Estimated: `1541`
|
||||
// Minimum execution time: 6_708_000 picoseconds.
|
||||
Weight::from_parts(6_971_000, 1541)
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `Salary::Status` (r:1 w:0)
|
||||
/// Proof: `Salary::Status` (`max_values`: Some(1), `max_size`: Some(56), added: 551, mode: `MaxEncodedLen`)
|
||||
/// Storage: `RankedCollective::Members` (r:1 w:0)
|
||||
/// Proof: `RankedCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Salary::Claimant` (r:1 w:1)
|
||||
/// Proof: `Salary::Claimant` (`max_values`: None, `max_size`: Some(78), added: 2553, mode: `MaxEncodedLen`)
|
||||
fn induct() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `177`
|
||||
// Estimated: `3543`
|
||||
// Minimum execution time: 14_418_000 picoseconds.
|
||||
Weight::from_parts(14_844_000, 3543)
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `RankedCollective::Members` (r:1 w:0)
|
||||
/// Proof: `RankedCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Salary::Status` (r:1 w:1)
|
||||
/// Proof: `Salary::Status` (`max_values`: Some(1), `max_size`: Some(56), added: 551, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Salary::Claimant` (r:1 w:1)
|
||||
/// Proof: `Salary::Claimant` (`max_values`: None, `max_size`: Some(78), added: 2553, mode: `MaxEncodedLen`)
|
||||
fn register() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `229`
|
||||
// Estimated: `3543`
|
||||
// Minimum execution time: 18_333_000 picoseconds.
|
||||
Weight::from_parts(18_876_000, 3543)
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: `Salary::Status` (r:1 w:1)
|
||||
/// Proof: `Salary::Status` (`max_values`: Some(1), `max_size`: Some(56), added: 551, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Salary::Claimant` (r:1 w:1)
|
||||
/// Proof: `Salary::Claimant` (`max_values`: None, `max_size`: Some(78), added: 2553, mode: `MaxEncodedLen`)
|
||||
/// Storage: `RankedCollective::Members` (r:1 w:0)
|
||||
/// Proof: `RankedCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`)
|
||||
fn payout() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `229`
|
||||
// Estimated: `3543`
|
||||
// Minimum execution time: 60_650_000 picoseconds.
|
||||
Weight::from_parts(61_965_000, 3543)
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: `Salary::Status` (r:1 w:1)
|
||||
/// Proof: `Salary::Status` (`max_values`: Some(1), `max_size`: Some(56), added: 551, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Salary::Claimant` (r:1 w:1)
|
||||
/// Proof: `Salary::Claimant` (`max_values`: None, `max_size`: Some(78), added: 2553, mode: `MaxEncodedLen`)
|
||||
/// Storage: `RankedCollective::Members` (r:1 w:0)
|
||||
/// Proof: `RankedCollective::Members` (`max_values`: None, `max_size`: Some(42), added: 2517, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
fn payout_other() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `229`
|
||||
// Estimated: `3593`
|
||||
// Minimum execution time: 60_860_000 picoseconds.
|
||||
Weight::from_parts(61_739_000, 3593)
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: `Salary::Status` (r:1 w:1)
|
||||
/// Proof: `Salary::Status` (`max_values`: Some(1), `max_size`: Some(56), added: 551, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Salary::Claimant` (r:1 w:1)
|
||||
/// Proof: `Salary::Claimant` (`max_values`: None, `max_size`: Some(78), added: 2553, mode: `MaxEncodedLen`)
|
||||
fn check_payment() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `95`
|
||||
// Estimated: `3543`
|
||||
// Minimum execution time: 9_559_000 picoseconds.
|
||||
Weight::from_parts(9_936_000, 3543)
|
||||
.saturating_add(RocksDbWeight::get().reads(2_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user