Files
pezkuwi-sdk/bizinikiwi/pezframe/delegated-staking/src/tests.rs
T
pezkuwichain 1c0e57d984 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.
2025-12-14 00:04:10 +03:00

1405 lines
43 KiB
Rust

// 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 pezpallet-delegated-staking.
use super::*;
use crate::mock::*;
use pezframe_support::{assert_noop, assert_ok, traits::fungible::InspectHold};
use pezpallet_nomination_pools::{Error as PoolsError, Event as PoolsEvent};
use pezpallet_staking::{Error as StakingError, RewardDestination};
use pezsp_staking::{Agent, DelegationInterface, Delegator, StakerStatus};
#[test]
fn create_an_agent_with_first_delegator() {
ExtBuilder::default().build_and_execute(|| {
let agent: AccountId = 200;
let reward_account: AccountId = 201;
let delegator: AccountId = 202;
// set intention to accept delegation.
fund(&agent, 1000);
assert_ok!(DelegatedStaking::register_agent(
RawOrigin::Signed(agent).into(),
reward_account
));
// delegate to this account
fund(&delegator, 1000);
assert_ok!(DelegatedStaking::delegate_to_agent(
RawOrigin::Signed(delegator).into(),
agent,
100
));
// verify
assert!(DelegatedStaking::is_agent(&agent));
assert_eq!(DelegatedStaking::stakeable_balance(Agent::from(agent)), 100);
assert_eq!(
Balances::balance_on_hold(&HoldReason::StakingDelegation.into(), &delegator),
100
);
assert_eq!(DelegatedStaking::held_balance_of(Delegator::from(delegator)), 100);
});
}
#[test]
fn cannot_become_agent() {
ExtBuilder::default().build_and_execute(|| {
// cannot set reward account same as agent account
assert_noop!(
DelegatedStaking::register_agent(RawOrigin::Signed(100).into(), 100),
Error::<T>::InvalidRewardDestination
);
});
}
#[test]
fn create_multiple_delegators() {
ExtBuilder::default().build_and_execute(|| {
let agent: AccountId = 200;
let reward_account: AccountId = 201;
// stakeable balance is 0 for non agent
fund(&agent, 1000);
assert!(!DelegatedStaking::is_agent(&agent));
assert_eq!(DelegatedStaking::stakeable_balance(Agent::from(agent)), 0);
// set intention to accept delegation.
assert_ok!(DelegatedStaking::register_agent(
RawOrigin::Signed(agent).into(),
reward_account
));
// create 100 delegators
for i in 202..302 {
fund(&i, 100 + ExistentialDeposit::get());
assert_ok!(DelegatedStaking::delegate_to_agent(
RawOrigin::Signed(i).into(),
agent,
100
));
// Balance of 100 held on delegator account for delegating to the agent.
assert_eq!(Balances::balance_on_hold(&HoldReason::StakingDelegation.into(), &i), 100);
}
// verify
assert!(DelegatedStaking::is_agent(&agent));
assert_eq!(DelegatedStaking::stakeable_balance(Agent::from(agent)), 100 * 100);
});
}
#[test]
fn agent_restrictions() {
// Similar to creating a nomination pool
ExtBuilder::default().build_and_execute(|| {
let agent_one = 200;
let delegator_one = 210;
fund(&agent_one, 100);
assert_ok!(DelegatedStaking::register_agent(
RawOrigin::Signed(agent_one).into(),
agent_one + 1
));
fund(&delegator_one, 200);
assert_ok!(DelegatedStaking::delegate_to_agent(
RawOrigin::Signed(delegator_one).into(),
agent_one,
100
));
let agent_two = 300;
let delegator_two = 310;
fund(&agent_two, 100);
assert_ok!(DelegatedStaking::register_agent(
RawOrigin::Signed(agent_two).into(),
agent_two + 1
));
fund(&delegator_two, 200);
assert_ok!(DelegatedStaking::delegate_to_agent(
RawOrigin::Signed(delegator_two).into(),
agent_two,
100
));
// agent one tries to delegate to agent 2
assert_noop!(
DelegatedStaking::delegate_to_agent(RawOrigin::Signed(agent_one).into(), agent_two, 10),
Error::<T>::InvalidDelegation
);
// agent one tries to delegate to a delegator
assert_noop!(
DelegatedStaking::delegate_to_agent(
RawOrigin::Signed(agent_one).into(),
delegator_one,
10
),
Error::<T>::InvalidDelegation
);
assert_noop!(
DelegatedStaking::delegate_to_agent(
RawOrigin::Signed(agent_one).into(),
delegator_two,
10
),
Error::<T>::InvalidDelegation
);
// delegator one tries to delegate to agent 2 as well (it already delegates to agent
// 1)
assert_noop!(
DelegatedStaking::delegate_to_agent(
RawOrigin::Signed(delegator_one).into(),
agent_two,
10
),
Error::<T>::InvalidDelegation
);
// cannot delegate to non agents.
let non_agent = 201;
// give it some funds
fund(&non_agent, 200);
assert_noop!(
DelegatedStaking::delegate_to_agent(
RawOrigin::Signed(delegator_one).into(),
non_agent,
10
),
Error::<T>::InvalidDelegation
);
// cannot delegate to a delegator
assert_noop!(
DelegatedStaking::delegate_to_agent(
RawOrigin::Signed(delegator_one).into(),
delegator_two,
10
),
Error::<T>::InvalidDelegation
);
// delegator cannot delegate to self
assert_noop!(
DelegatedStaking::delegate_to_agent(
RawOrigin::Signed(delegator_one).into(),
delegator_one,
10
),
Error::<T>::InvalidDelegation
);
// agent cannot delegate to self
assert_noop!(
DelegatedStaking::delegate_to_agent(RawOrigin::Signed(agent_one).into(), agent_one, 10),
Error::<T>::InvalidDelegation
);
});
}
#[test]
fn apply_pending_slash() {
ExtBuilder::default().build_and_execute(|| {
start_era(1);
let agent: AccountId = 200;
let reward_acc: AccountId = 201;
let delegators: Vec<AccountId> = (301..=350).collect();
let reporter: AccountId = 400;
let total_staked = setup_delegation_stake(agent, reward_acc, delegators.clone(), 10, 10);
start_era(4);
// slash half of the stake
pezpallet_staking::slashing::do_slash::<T>(
&agent,
total_staked / 2,
&mut Default::default(),
&mut Default::default(),
3,
);
// agent cannot slash an account that is not its delegator.
setup_delegation_stake(210, 211, (351..=352).collect(), 100, 0);
assert_noop!(
<DelegatedStaking as DelegationInterface>::delegator_slash(
Agent::from(agent),
Delegator::from(351),
1,
Some(400)
),
Error::<T>::NotAgent
);
// or a non delegator account
fund(&353, 100);
assert_noop!(
<DelegatedStaking as DelegationInterface>::delegator_slash(
Agent::from(agent),
Delegator::from(353),
1,
Some(400)
),
Error::<T>::NotDelegator
);
// ensure bookkept pending slash is correct.
assert_eq!(get_agent_ledger(&agent).ledger.pending_slash, total_staked / 2);
let mut old_reporter_balance = Balances::free_balance(reporter);
// lets apply the pending slash on delegators.
for i in delegators {
// balance before slash
let initial_pending_slash = get_agent_ledger(&agent).ledger.pending_slash;
assert!(initial_pending_slash > 0);
let unslashed_balance = DelegatedStaking::held_balance_of(Delegator::from(i));
let slash = unslashed_balance / 2;
// slash half of delegator's delegation.
assert_ok!(<DelegatedStaking as DelegationInterface>::delegator_slash(
Agent::from(agent),
Delegator::from(i),
slash,
Some(400)
));
// balance after slash.
assert_eq!(
DelegatedStaking::held_balance_of(Delegator::from(i)),
unslashed_balance - slash
);
// pending slash is reduced by the amount slashed.
assert_eq!(
get_agent_ledger(&agent).ledger.pending_slash,
initial_pending_slash - slash
);
// reporter get 10% of the slash amount.
assert_eq!(
Balances::free_balance(reporter) - old_reporter_balance,
<Staking as StakingInterface>::slash_reward_fraction() * slash,
);
// update old balance
old_reporter_balance = Balances::free_balance(reporter);
}
// nothing to slash anymore
assert_eq!(get_agent_ledger(&agent).ledger.pending_slash, 0);
// cannot slash anymore
assert_noop!(
<DelegatedStaking as DelegationInterface>::delegator_slash(
Agent::from(agent),
Delegator::from(350),
1,
None
),
Error::<T>::NothingToSlash
);
});
}
#[test]
fn allow_full_amount_to_be_delegated() {
ExtBuilder::default().build_and_execute(|| {
let agent: AccountId = 200;
let reward_acc: AccountId = 201;
let delegator: AccountId = 300;
// set intention to accept delegation.
fund(&agent, 1000);
assert_ok!(DelegatedStaking::register_agent(RawOrigin::Signed(agent).into(), reward_acc));
// delegate to this account
fund(&delegator, 1000);
assert_ok!(DelegatedStaking::delegate_to_agent(
RawOrigin::Signed(delegator).into(),
agent,
1000
));
// verify
assert!(DelegatedStaking::is_agent(&agent));
assert_eq!(DelegatedStaking::stakeable_balance(Agent::from(agent)), 1000);
assert_eq!(
Balances::balance_on_hold(&HoldReason::StakingDelegation.into(), &delegator),
1000
);
assert_eq!(DelegatedStaking::held_balance_of(Delegator::from(delegator)), 1000);
});
}
/// Integration tests with pezpallet-staking.
mod staking_integration {
use super::*;
use pezsp_staking::Stake;
#[test]
fn bond() {
ExtBuilder::default().build_and_execute(|| {
let agent: AccountId = 99;
let reward_acc: AccountId = 100;
assert_eq!(Staking::status(&agent), Err(StakingError::<T>::NotStash.into()));
// set intention to become an agent
fund(&agent, 100);
assert_ok!(DelegatedStaking::register_agent(
RawOrigin::Signed(agent).into(),
reward_acc
));
assert_eq!(DelegatedStaking::stakeable_balance(Agent::from(agent)), 0);
let mut delegated_balance: Balance = 0;
// set some delegations
for delegator in 200..250 {
fund(&delegator, 200);
assert_ok!(DelegatedStaking::delegate_to_agent(
RawOrigin::Signed(delegator).into(),
agent,
100
));
delegated_balance += 100;
assert_eq!(
Balances::balance_on_hold(&HoldReason::StakingDelegation.into(), &delegator),
100
);
assert_eq!(
DelegatedStaking::delegator_balance(Delegator::from(delegator)).unwrap(),
100
);
let agent_obj = get_agent_ledger(&agent);
assert_eq!(agent_obj.ledger.stakeable_balance(), delegated_balance);
assert_eq!(agent_obj.available_to_bond(), 0);
assert_eq!(agent_obj.bonded_stake(), delegated_balance);
}
assert_eq!(Staking::stake(&agent).unwrap(), Stake { total: 50 * 100, active: 50 * 100 })
});
}
#[test]
fn withdraw_test() {
ExtBuilder::default().build_and_execute(|| {
// initial era
start_era(1);
let agent: AccountId = 200;
let reward_acc: AccountId = 201;
let delegators: Vec<AccountId> = (301..=350).collect();
let total_staked =
setup_delegation_stake(agent, reward_acc, delegators.clone(), 10, 10);
// lets go to a new era
start_era(2);
assert!(eq_stake(agent, total_staked, total_staked));
// Withdrawing without unbonding would fail.
assert_noop!(
DelegatedStaking::release_delegation(RawOrigin::Signed(agent).into(), 301, 50, 0),
Error::<T>::NotEnoughFunds
);
// 305 wants to unbond 50 in era 2, withdrawable in era 5.
assert_ok!(Staking::unbond(RawOrigin::Signed(agent).into(), 50));
// 310 wants to unbond 100 in era 3, withdrawable in era 6.
start_era(3);
assert_ok!(Staking::unbond(RawOrigin::Signed(agent).into(), 100));
// 320 wants to unbond 200 in era 4, withdrawable in era 7.
start_era(4);
assert_ok!(Staking::unbond(RawOrigin::Signed(agent).into(), 200));
// active stake is now reduced..
let expected_active = total_staked - (50 + 100 + 200);
assert!(eq_stake(agent, total_staked, expected_active));
// nothing to withdraw at era 4
assert_noop!(
DelegatedStaking::release_delegation(RawOrigin::Signed(agent).into(), 305, 50, 0),
Error::<T>::NotEnoughFunds
);
assert_eq!(get_agent_ledger(&agent).available_to_bond(), 0);
// full amount is still delegated
assert_eq!(get_agent_ledger(&agent).ledger.effective_balance(), total_staked);
start_era(5);
// at era 5, 50 tokens are withdrawable, cannot withdraw more.
assert_noop!(
DelegatedStaking::release_delegation(RawOrigin::Signed(agent).into(), 305, 51, 0),
Error::<T>::NotEnoughFunds
);
// less is possible
assert_ok!(DelegatedStaking::release_delegation(
RawOrigin::Signed(agent).into(),
305,
30,
0
));
assert_ok!(DelegatedStaking::release_delegation(
RawOrigin::Signed(agent).into(),
305,
20,
0
));
// Lets go to future era where everything is unbonded. Withdrawable amount: 100 + 200
start_era(7);
// 305 has no more amount delegated so it cannot withdraw.
assert_noop!(
DelegatedStaking::release_delegation(RawOrigin::Signed(agent).into(), 305, 5, 0),
Error::<T>::NotDelegator
);
// 309 is an active delegator but has total delegation of 90, so it cannot withdraw more
// than that.
assert_noop!(
DelegatedStaking::release_delegation(RawOrigin::Signed(agent).into(), 309, 91, 0),
Error::<T>::NotEnoughFunds
);
// 310 cannot withdraw more than delegated funds.
assert_noop!(
DelegatedStaking::release_delegation(RawOrigin::Signed(agent).into(), 310, 101, 0),
Error::<T>::NotEnoughFunds
);
// but can withdraw all its delegation amount.
assert_ok!(DelegatedStaking::release_delegation(
RawOrigin::Signed(agent).into(),
310,
100,
0
));
// 320 can withdraw all its delegation amount.
assert_ok!(DelegatedStaking::release_delegation(
RawOrigin::Signed(agent).into(),
320,
200,
0
));
// cannot withdraw anything more..
assert_noop!(
DelegatedStaking::release_delegation(RawOrigin::Signed(agent).into(), 301, 1, 0),
Error::<T>::NotEnoughFunds
);
assert_noop!(
DelegatedStaking::release_delegation(RawOrigin::Signed(agent).into(), 350, 1, 0),
Error::<T>::NotEnoughFunds
);
});
}
#[test]
fn withdraw_happens_with_unbonded_balance_first() {
ExtBuilder::default().build_and_execute(|| {
start_era(1);
let agent = 200;
setup_delegation_stake(agent, 201, (300..350).collect(), 320, 0);
// verify withdraw not possible yet
assert_noop!(
DelegatedStaking::release_delegation(RawOrigin::Signed(agent).into(), 300, 320, 0),
Error::<T>::NotEnoughFunds
);
// fill up unlocking chunks in core staking.
// 32 is the max chunks
for i in 2..=33 {
start_era(i);
assert_ok!(Staking::unbond(RawOrigin::Signed(agent).into(), 10));
// no withdrawals from core staking yet.
assert_eq!(get_agent_ledger(&agent).ledger.unclaimed_withdrawals, 0);
}
// another unbond would trigger withdrawal
start_era(34);
assert_ok!(Staking::unbond(RawOrigin::Signed(agent).into(), 10));
// 30 previous unbonds would be withdrawn as they were already unlocked. Unlocking
// period is 3 eras.
assert_eq!(get_agent_ledger(&agent).ledger.unclaimed_withdrawals, 30 * 10);
// release some delegation now.
assert_ok!(DelegatedStaking::release_delegation(
RawOrigin::Signed(agent).into(),
300,
160,
0
));
assert_eq!(get_agent_ledger(&agent).ledger.unclaimed_withdrawals, 300 - 160);
// cannot release more than available
assert_noop!(
DelegatedStaking::release_delegation(RawOrigin::Signed(agent).into(), 300, 141, 0),
Error::<T>::NotEnoughFunds
);
assert_ok!(DelegatedStaking::release_delegation(
RawOrigin::Signed(agent).into(),
300,
140,
0
));
assert_eq!(DelegatedStaking::held_balance_of(Delegator::from(300)), 320 - 300);
});
}
#[test]
fn reward_destination_restrictions() {
ExtBuilder::default().build_and_execute(|| {
// give some funds to 200
fund(&200, 1000);
let balance_200 = Balances::free_balance(200);
// `Agent` account cannot be reward destination
assert_noop!(
DelegatedStaking::register_agent(RawOrigin::Signed(200).into(), 200),
Error::<T>::InvalidRewardDestination
);
// different reward account works
assert_ok!(DelegatedStaking::register_agent(RawOrigin::Signed(200).into(), 201));
// add some delegations to it
fund(&300, 1000);
assert_ok!(DelegatedStaking::delegate_to_agent(
RawOrigin::Signed(300).into(),
200,
100
));
// set_payee to self fails.
assert_noop!(
<Staking as StakingInterface>::set_payee(&200, &200),
StakingError::<T>::RewardDestinationRestricted
);
// passing correct reward destination works
assert_ok!(<Staking as StakingInterface>::set_payee(&200, &201));
// amount is staked correctly
assert!(eq_stake(200, 100, 100));
assert_eq!(get_agent_ledger(&200).available_to_bond(), 0);
assert_eq!(get_agent_ledger(&200).ledger.effective_balance(), 100);
// free balance of delegate is untouched
assert_eq!(Balances::free_balance(200), balance_200);
});
}
#[test]
fn agent_restrictions() {
ExtBuilder::default().build_and_execute(|| {
setup_delegation_stake(200, 201, (202..203).collect(), 100, 0);
// Registering again is noop
assert_noop!(
DelegatedStaking::register_agent(RawOrigin::Signed(200).into(), 201),
Error::<T>::NotAllowed
);
// a delegator cannot become delegate
assert_noop!(
DelegatedStaking::register_agent(RawOrigin::Signed(202).into(), 203),
Error::<T>::NotAllowed
);
});
}
#[test]
fn migration_works() {
ExtBuilder::default().build_and_execute(|| {
// initial era
start_era(1);
// add a nominator
let staked_amount = 4000;
let agent_amount = 5000;
let agent = 200;
fund(&agent, agent_amount);
assert_ok!(Staking::bond(
RuntimeOrigin::signed(agent),
staked_amount,
RewardDestination::Account(201)
));
assert_ok!(Staking::nominate(RuntimeOrigin::signed(agent), vec![GENESIS_VALIDATOR],));
let init_stake = Staking::stake(&agent).unwrap();
// no extra provider added.
assert_eq!(System::providers(&agent), 1);
// scenario: 200 is a pool account, and the stake comes from its 4 delegators (300..304)
// in equal parts. lets try to migrate this nominator into delegate based stake.
// all balance currently is in 200
assert_eq!(pezpallet_staking::asset::total_balance::<T>(&agent), agent_amount);
// to migrate, nominator needs to set an account as a proxy delegator where staked funds
// will be moved and delegated back to this old nominator account. This should be funded
// with at least ED.
let proxy_delegator =
DelegatedStaking::generate_proxy_delegator(Agent::from(agent)).get();
assert_ok!(DelegatedStaking::migrate_to_agent(RawOrigin::Signed(agent).into(), 201));
// after migration, no provider left since free balance is 0 and staking pallet released
// all funds.
assert_eq!(System::providers(&agent), 0);
assert_eq!(Balances::free_balance(agent), 0);
// proxy delegator has one provider as well with no free balance.
assert_eq!(System::providers(&proxy_delegator), 1);
assert_eq!(Balances::free_balance(proxy_delegator), 0);
// verify all went well
let mut expected_proxy_delegated_amount = agent_amount;
assert_eq!(
Balances::balance_on_hold(&HoldReason::StakingDelegation.into(), &proxy_delegator),
expected_proxy_delegated_amount
);
// stake amount is transferred from delegate to proxy delegator account.
assert_eq!(Balances::free_balance(agent), 0);
assert_eq!(Staking::stake(&agent).unwrap(), init_stake);
assert_eq!(get_agent_ledger(&agent).ledger.effective_balance(), agent_amount);
assert_eq!(get_agent_ledger(&agent).available_to_bond(), 0);
assert_eq!(
get_agent_ledger(&agent).ledger.unclaimed_withdrawals,
agent_amount - staked_amount
);
// now lets migrate the delegators
let delegator_share = agent_amount / 4;
for delegator in 300..304 {
assert_eq!(Balances::free_balance(delegator), 0);
assert_eq!(System::providers(&delegator), 0);
// No pre-balance needed to migrate delegator.
assert_ok!(DelegatedStaking::migrate_delegation(
RawOrigin::Signed(agent).into(),
delegator,
delegator_share
));
assert_eq!(System::providers(&delegator), 1);
assert_eq!(
Balances::balance_on_hold(&HoldReason::StakingDelegation.into(), &delegator),
delegator_share
);
expected_proxy_delegated_amount -= delegator_share;
assert_eq!(
Balances::balance_on_hold(
&HoldReason::StakingDelegation.into(),
&proxy_delegator
),
expected_proxy_delegated_amount
);
// delegate stake is unchanged.
assert_eq!(Staking::stake(&agent).unwrap(), init_stake);
assert_eq!(get_agent_ledger(&agent).ledger.effective_balance(), agent_amount);
assert_eq!(get_agent_ledger(&agent).available_to_bond(), 0);
assert_eq!(
get_agent_ledger(&agent).ledger.unclaimed_withdrawals,
agent_amount - staked_amount
);
}
// cannot use migrate delegator anymore
assert_noop!(
DelegatedStaking::migrate_delegation(RawOrigin::Signed(agent).into(), 305, 1),
Error::<T>::NotEnoughFunds
);
// no provider left on proxy delegator since all funds are migrated
assert_eq!(System::providers(&proxy_delegator), 0);
// withdraw all delegations from delegators
assert_ok!(Staking::chill(RuntimeOrigin::signed(agent)));
assert_ok!(Staking::unbond(RawOrigin::Signed(agent).into(), staked_amount));
start_era(4);
assert_ok!(Staking::withdraw_unbonded(RawOrigin::Signed(agent).into(), 0));
for delegator in 300..304 {
assert_ok!(DelegatedStaking::release_delegation(
RawOrigin::Signed(agent).into(),
delegator,
delegator_share,
0
));
// delegator is cleaned up from storage.
assert!(!Delegators::<T>::contains_key(delegator));
// has free balance now
assert_eq!(Balances::free_balance(delegator), delegator_share);
// and only one provider as delegator_share > ED
assert_eq!(System::providers(&delegator), 1);
}
// Agent can be removed now.
assert_ok!(DelegatedStaking::remove_agent(RawOrigin::Signed(agent).into()));
// agent is correctly removed.
assert!(!Agents::<T>::contains_key(agent));
// and no provider left.
assert_eq!(System::providers(&agent), 0);
});
}
#[test]
fn accounts_are_cleaned_up() {
ExtBuilder::default().build_and_execute(|| {
let agent: AccountId = 200;
let reward_acc: AccountId = 201;
let delegator: AccountId = 300;
// set intention to accept delegation.
fund(&agent, 1000);
// Agent is provided since it has balance > ED.
assert_eq!(System::providers(&agent), 1);
assert_ok!(DelegatedStaking::register_agent(
RawOrigin::Signed(agent).into(),
reward_acc
));
// delegate to this account
fund(&delegator, 1000);
// account has one provider since its funded.
assert_eq!(System::providers(&delegator), 1);
assert_ok!(DelegatedStaking::delegate_to_agent(
RawOrigin::Signed(delegator).into(),
agent,
500
));
// delegator has an extra provider now.
assert_eq!(System::providers(&delegator), 2);
// all 1000 tokens including ED can be held.
assert_ok!(DelegatedStaking::delegate_to_agent(
RawOrigin::Signed(delegator).into(),
agent,
500
));
// free balance dropping below ED will reduce a provider, but it still has one provider
// left.
assert_eq!(System::providers(&delegator), 1);
// withdraw all delegation
assert_ok!(Staking::unbond(RawOrigin::Signed(agent).into(), 1000));
start_era(4);
assert_ok!(Staking::withdraw_unbonded(RawOrigin::Signed(agent).into(), 0));
// Since delegations are still left, agents cannot be removed yet from storage.
assert_noop!(
DelegatedStaking::remove_agent(RawOrigin::Signed(agent).into()),
Error::<T>::NotAllowed
);
assert_ok!(DelegatedStaking::release_delegation(
RawOrigin::Signed(agent).into(),
delegator,
1000,
0
));
// now agents can be removed.
assert_ok!(DelegatedStaking::remove_agent(RawOrigin::Signed(agent).into()));
// agent and delegator provider is decremented.
assert_eq!(System::providers(&delegator), 1);
assert_eq!(System::providers(&agent), 1);
// if we transfer all funds, providers are removed.
assert_ok!(Balances::transfer_all(RawOrigin::Signed(delegator).into(), 1337, false));
assert_ok!(Balances::transfer_all(RawOrigin::Signed(agent).into(), 1337, false));
assert_eq!(System::providers(&delegator), 0);
assert_eq!(System::providers(&agent), 0);
});
}
}
mod pool_integration {
use super::*;
use pezpallet_nomination_pools::{BondExtra, BondedPools, PoolState};
#[test]
fn create_pool_test() {
ExtBuilder::default().build_and_execute(|| {
let creator: AccountId = 100;
fund(&creator, 500);
let delegate_amount = 200;
// nothing held initially
assert_eq!(DelegatedStaking::held_balance_of(Delegator::from(creator)), 0);
// create pool
assert_ok!(Pools::create(
RawOrigin::Signed(creator).into(),
delegate_amount,
creator,
creator,
creator
));
// correct amount is locked in depositor's account.
assert_eq!(
DelegatedStaking::held_balance_of(Delegator::from(creator)),
delegate_amount
);
let pool_account = Pools::generate_bonded_account(1);
let agent = get_agent_ledger(&pool_account);
// verify state
assert_eq!(agent.ledger.effective_balance(), delegate_amount);
assert_eq!(agent.available_to_bond(), 0);
assert_eq!(agent.total_unbonded(), 0);
});
}
#[test]
fn join_pool() {
ExtBuilder::default().build_and_execute(|| {
// create a pool
let pool_id = create_pool(100, 200);
// keep track of staked amount.
let mut staked_amount: Balance = 200;
// fund delegator
let delegator: AccountId = 300;
fund(&delegator, 500);
// nothing held initially
assert_eq!(DelegatedStaking::held_balance_of(Delegator::from(delegator)), 0);
// delegator joins pool
assert_ok!(Pools::join(RawOrigin::Signed(delegator).into(), 100, pool_id));
staked_amount += 100;
// correct amount is locked in depositor's account.
assert_eq!(DelegatedStaking::held_balance_of(Delegator::from(delegator)), 100);
// delegator is not actively exposed to core staking.
assert_eq!(Staking::status(&delegator), Err(StakingError::<T>::NotStash.into()));
let pool_agent = get_agent_ledger(&Pools::generate_bonded_account(1));
// verify state
assert_eq!(pool_agent.ledger.effective_balance(), staked_amount);
assert_eq!(pool_agent.bonded_stake(), staked_amount);
assert_eq!(pool_agent.available_to_bond(), 0);
assert_eq!(pool_agent.total_unbonded(), 0);
// cannot reap agent in staking.
assert_noop!(
Staking::reap_stash(RuntimeOrigin::signed(100), pool_agent.key, 0),
StakingError::<T>::VirtualStakerNotAllowed
);
// let a bunch of delegators join this pool
for i in 301..350 {
fund(&i, 500);
assert_ok!(Pools::join(RawOrigin::Signed(i).into(), 100 + i, pool_id));
staked_amount += 100 + i;
assert_eq!(DelegatedStaking::held_balance_of(Delegator::from(i)), 100 + i);
}
let pool_agent = pool_agent.reload().unwrap();
assert_eq!(pool_agent.ledger.effective_balance(), staked_amount);
assert_eq!(pool_agent.bonded_stake(), staked_amount);
assert_eq!(pool_agent.available_to_bond(), 0);
assert_eq!(pool_agent.total_unbonded(), 0);
});
}
#[test]
fn bond_extra_to_pool() {
ExtBuilder::default().build_and_execute(|| {
let pool_id = create_pool(100, 200);
add_delegators_to_pool(pool_id, (300..310).collect(), 100);
let mut staked_amount = 200 + 100 * 10;
assert_eq!(get_pool_agent(pool_id).bonded_stake(), staked_amount);
// bond extra to pool
for i in 300..310 {
assert_ok!(Pools::bond_extra(
RawOrigin::Signed(i).into(),
BondExtra::FreeBalance(50)
));
staked_amount += 50;
assert_eq!(get_pool_agent(pool_id).bonded_stake(), staked_amount);
}
});
}
#[test]
fn claim_pool_rewards() {
ExtBuilder::default().build_and_execute(|| {
let creator = 100;
let creator_stake = 1000;
let pool_id = create_pool(creator, creator_stake);
add_delegators_to_pool(pool_id, (300..310).collect(), 100);
add_delegators_to_pool(pool_id, (310..320).collect(), 200);
let total_staked = creator_stake + 100 * 10 + 200 * 10;
// give some rewards
let reward_acc = Pools::generate_reward_account(pool_id);
let reward_amount = 1000;
fund(&reward_acc, reward_amount);
// claim rewards
for i in 300..320 {
let pre_balance = Balances::free_balance(i);
let delegator_staked_balance =
DelegatedStaking::held_balance_of(Delegator::from(i));
// payout reward
assert_ok!(Pools::claim_payout(RawOrigin::Signed(i).into()));
let reward = Balances::free_balance(i) - pre_balance;
assert_eq!(reward, delegator_staked_balance * reward_amount / total_staked);
}
// payout creator
let pre_balance = Balances::free_balance(creator);
assert_ok!(Pools::claim_payout(RawOrigin::Signed(creator).into()));
// verify they are paid out correctly
let reward = Balances::free_balance(creator) - pre_balance;
assert_eq!(reward, creator_stake * reward_amount / total_staked);
// reward account should only have left minimum balance after paying out everyone.
assert_eq!(Balances::free_balance(reward_acc), ExistentialDeposit::get());
});
}
#[test]
fn withdraw_from_pool() {
ExtBuilder::default().build_and_execute(|| {
// initial era
start_era(1);
let pool_id = create_pool(100, 1000);
let bond_amount = 200;
add_delegators_to_pool(pool_id, (300..310).collect(), bond_amount);
let total_staked = 1000 + bond_amount * 10;
let pool_acc = Pools::generate_bonded_account(pool_id);
start_era(2);
// nothing to release yet.
assert_noop!(
Pools::withdraw_unbonded(RawOrigin::Signed(301).into(), 301, 0),
PoolsError::<T>::SubPoolsNotFound
);
// 301 wants to unbond 50 in era 2, withdrawable in era 5.
assert_ok!(Pools::unbond(RawOrigin::Signed(301).into(), 301, 50));
// 302 wants to unbond 100 in era 3, withdrawable in era 6.
start_era(3);
assert_ok!(Pools::unbond(RawOrigin::Signed(302).into(), 302, 100));
// 303 wants to unbond 200 in era 4, withdrawable in era 7.
start_era(4);
assert_ok!(Pools::unbond(RawOrigin::Signed(303).into(), 303, 200));
// active stake is now reduced..
let expected_active = total_staked - (50 + 100 + 200);
assert!(eq_stake(pool_acc, total_staked, expected_active));
// nothing to withdraw at era 4
for i in 301..310 {
assert_noop!(
Pools::withdraw_unbonded(RawOrigin::Signed(i).into(), i, 0),
PoolsError::<T>::CannotWithdrawAny
);
}
assert!(eq_stake(pool_acc, total_staked, expected_active));
start_era(5);
// at era 5, 301 can withdraw.
System::reset_events();
let held_301 = DelegatedStaking::held_balance_of(Delegator::from(301));
let free_301 = Balances::free_balance(301);
assert_ok!(Pools::withdraw_unbonded(RawOrigin::Signed(301).into(), 301, 0));
assert_eq!(
events_since_last_call(),
vec![Event::Released { agent: pool_acc, delegator: 301, amount: 50 }]
);
assert_eq!(
pool_events_since_last_call(),
vec![PoolsEvent::Withdrawn { member: 301, pool_id, balance: 50, points: 50 }]
);
assert_eq!(DelegatedStaking::held_balance_of(Delegator::from(301)), held_301 - 50);
assert_eq!(Balances::free_balance(301), free_301 + 50);
start_era(7);
// era 7 both delegators can withdraw
assert_ok!(Pools::withdraw_unbonded(RawOrigin::Signed(302).into(), 302, 0));
assert_ok!(Pools::withdraw_unbonded(RawOrigin::Signed(303).into(), 303, 0));
assert_eq!(
events_since_last_call(),
vec![
Event::Released { agent: pool_acc, delegator: 302, amount: 100 },
Event::Released { agent: pool_acc, delegator: 303, amount: 200 },
]
);
assert_eq!(
pool_events_since_last_call(),
vec![
PoolsEvent::Withdrawn { member: 302, pool_id, balance: 100, points: 100 },
PoolsEvent::Withdrawn { member: 303, pool_id, balance: 200, points: 200 },
PoolsEvent::MemberRemoved { pool_id: 1, member: 303, released_balance: 0 },
]
);
// 303 is killed
assert!(!Delegators::<T>::contains_key(303));
});
}
#[test]
fn pool_withdraw_unbonded() {
ExtBuilder::default().build_and_execute(|| {
// initial era
start_era(1);
let pool_id = create_pool(100, 1000);
add_delegators_to_pool(pool_id, (300..310).collect(), 200);
start_era(2);
// 1000 tokens to be unbonded in era 5.
for i in 300..310 {
assert_ok!(Pools::unbond(RawOrigin::Signed(i).into(), i, 100));
}
start_era(3);
// 500 tokens to be unbonded in era 6.
for i in 300..310 {
assert_ok!(Pools::unbond(RawOrigin::Signed(i).into(), i, 50));
}
start_era(5);
// withdraw pool should withdraw 1000 tokens
assert_ok!(Pools::pool_withdraw_unbonded(RawOrigin::Signed(100).into(), pool_id, 0));
assert_eq!(get_pool_agent(pool_id).total_unbonded(), 1000);
start_era(6);
// should withdraw 500 more
assert_ok!(Pools::pool_withdraw_unbonded(RawOrigin::Signed(100).into(), pool_id, 0));
assert_eq!(get_pool_agent(pool_id).total_unbonded(), 1000 + 500);
start_era(7);
// Nothing to withdraw, still at 1500.
assert_ok!(Pools::pool_withdraw_unbonded(RawOrigin::Signed(100).into(), pool_id, 0));
assert_eq!(get_pool_agent(pool_id).total_unbonded(), 1500);
});
}
#[test]
fn update_nominations() {
ExtBuilder::default().build_and_execute(|| {
start_era(1);
// can't nominate for non-existent pool
assert_noop!(
Pools::nominate(RawOrigin::Signed(100).into(), 1, vec![99]),
PoolsError::<T>::PoolNotFound
);
let pool_id = create_pool(100, 1000);
let pool_acc = Pools::generate_bonded_account(pool_id);
assert_ok!(Pools::nominate(RawOrigin::Signed(100).into(), 1, vec![20, 21, 22]));
assert!(Staking::status(&pool_acc) == Ok(StakerStatus::Nominator(vec![20, 21, 22])));
start_era(3);
assert_ok!(Pools::nominate(RawOrigin::Signed(100).into(), 1, vec![18, 19, 22]));
assert!(Staking::status(&pool_acc) == Ok(StakerStatus::Nominator(vec![18, 19, 22])));
});
}
#[test]
fn destroy_pool() {
ExtBuilder::default().build_and_execute(|| {
start_era(1);
let creator = 100;
let creator_stake = 1000;
let pool_id = create_pool(creator, creator_stake);
add_delegators_to_pool(pool_id, (300..310).collect(), 200);
start_era(3);
// lets destroy the pool
assert_ok!(Pools::set_state(
RawOrigin::Signed(creator).into(),
pool_id,
PoolState::Destroying
));
assert_ok!(Pools::chill(RawOrigin::Signed(creator).into(), pool_id));
// unbond all members by the creator/admin
for i in 300..310 {
assert_ok!(Pools::unbond(RawOrigin::Signed(creator).into(), i, 200));
}
start_era(6);
// withdraw all members by the creator/admin
for i in 300..310 {
assert_ok!(Pools::withdraw_unbonded(RawOrigin::Signed(creator).into(), i, 0));
}
// unbond creator
assert_ok!(Pools::unbond(RawOrigin::Signed(creator).into(), creator, creator_stake));
start_era(9);
System::reset_events();
// Withdraw self
assert_ok!(Pools::withdraw_unbonded(RawOrigin::Signed(creator).into(), creator, 0));
assert_eq!(
pool_events_since_last_call(),
vec![
PoolsEvent::Withdrawn {
member: creator,
pool_id,
balance: creator_stake,
points: creator_stake,
},
PoolsEvent::MemberRemoved { pool_id, member: creator, released_balance: 0 },
PoolsEvent::Destroyed { pool_id },
]
);
// Make sure all data is cleaned up.
assert!(!Agents::<T>::contains_key(Pools::generate_bonded_account(pool_id)));
assert!(!System::account_exists(&Pools::generate_bonded_account(pool_id)));
assert!(!Delegators::<T>::contains_key(creator));
for i in 300..310 {
assert!(!Delegators::<T>::contains_key(i));
}
});
}
#[test]
fn pool_partially_slashed() {
ExtBuilder::default().build_and_execute(|| {
start_era(1);
let creator = 100;
let creator_stake = 500;
let pool_id = create_pool(creator, creator_stake);
let delegator_stake = 100;
add_delegators_to_pool(pool_id, (300..306).collect(), delegator_stake);
let pool_acc = Pools::generate_bonded_account(pool_id);
let total_staked = creator_stake + delegator_stake * 6;
assert_eq!(Staking::stake(&pool_acc).unwrap().total, total_staked);
// lets unbond a delegator each in next eras (2, 3, 4).
start_era(2);
assert_ok!(Pools::unbond(RawOrigin::Signed(300).into(), 300, delegator_stake));
start_era(3);
assert_ok!(Pools::unbond(RawOrigin::Signed(301).into(), 301, delegator_stake));
start_era(4);
assert_ok!(Pools::unbond(RawOrigin::Signed(302).into(), 302, delegator_stake));
System::reset_events();
// slash the pool at era 3
assert_eq!(
BondedPools::<T>::get(1).unwrap().points,
creator_stake + delegator_stake * 6 - delegator_stake * 3
);
// pool has currently no pending slash
assert_eq!(Pools::api_pool_pending_slash(pool_id), 0);
// slash the pool partially
pezpallet_staking::slashing::do_slash::<T>(
&pool_acc,
500,
&mut Default::default(),
&mut Default::default(),
3,
);
// pool has now pending slash of 500.
assert_eq!(Pools::api_pool_pending_slash(pool_id), 500);
assert_eq!(
pool_events_since_last_call(),
vec![
// 300 did not get slashed as all as it unbonded in an era before slash.
// 301 got slashed 50% of 100 = 50.
PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 6, balance: 50 },
// 302 got slashed 50% of 100 = 50.
PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 7, balance: 50 },
// Rest of the pool slashed 50% of 800 = 400.
PoolsEvent::PoolSlashed { pool_id: 1, balance: 400 },
]
);
// slash is lazy and balance is still locked in user's accounts.
assert_eq!(DelegatedStaking::held_balance_of(Delegator::from(creator)), creator_stake);
for i in 300..306 {
assert_eq!(DelegatedStaking::held_balance_of(Delegator::from(i)), delegator_stake);
}
assert_eq!(
get_pool_agent(pool_id).ledger.effective_balance(),
Staking::total_stake(&pool_acc).unwrap()
);
// pending slash is book kept.
assert_eq!(get_pool_agent(pool_id).ledger.pending_slash, 500);
// go in some distant future era.
start_era(10);
System::reset_events();
// 300 is not slashed and can withdraw all balance.
assert_ok!(Pools::withdraw_unbonded(RawOrigin::Signed(300).into(), 300, 1));
assert_eq!(
events_since_last_call(),
vec![Event::Released { agent: pool_acc, delegator: 300, amount: 100 }]
);
assert_eq!(get_pool_agent(pool_id).ledger.pending_slash, 500);
// withdraw the other two delegators (301 and 302) who were unbonding.
for i in 301..=302 {
let pre_balance = Balances::free_balance(i);
let pre_pending_slash = get_pool_agent(pool_id).ledger.pending_slash;
assert_ok!(Pools::withdraw_unbonded(RawOrigin::Signed(i).into(), i, 0));
assert_eq!(
events_since_last_call(),
vec![
Event::Slashed { agent: pool_acc, delegator: i, amount: 50 },
Event::Released { agent: pool_acc, delegator: i, amount: 50 },
]
);
assert_eq!(get_pool_agent(pool_id).ledger.pending_slash, pre_pending_slash - 50);
assert_eq!(DelegatedStaking::held_balance_of(Delegator::from(i)), 0);
assert_eq!(Balances::free_balance(i) - pre_balance, 50);
}
// let's update all the slash
let slash_reporter = 99;
// give our reporter some balance.
fund(&slash_reporter, 100);
for i in 303..306 {
let pre_pending_slash = get_pool_agent(pool_id).ledger.pending_slash;
// pool api returns correct pending slash.
assert_eq!(Pools::api_pool_pending_slash(pool_id), pre_pending_slash);
// delegator has pending slash of 50.
assert_eq!(Pools::api_member_pending_slash(i), 50);
// apply slash
assert_ok!(Pools::apply_slash(RawOrigin::Signed(slash_reporter).into(), i));
// nothing pending anymore.
assert_eq!(Pools::api_member_pending_slash(i), 0);
// each member is slashed 50% of 100 = 50.
assert_eq!(get_pool_agent(pool_id).ledger.pending_slash, pre_pending_slash - 50);
// pool api returns correctly as well.
assert_eq!(Pools::api_pool_pending_slash(pool_id), pre_pending_slash - 50);
// left with 50.
assert_eq!(DelegatedStaking::held_balance_of(Delegator::from(i)), 50);
}
// pool has still pending slash of creator.
assert_eq!(Pools::api_pool_pending_slash(pool_id), 250);
// reporter is paid SlashRewardFraction of the slash, i.e. 10% of 50 = 5
assert_eq!(Balances::free_balance(slash_reporter), 100 + 5 * 3);
// creator has pending slash.
assert_eq!(Pools::api_member_pending_slash(creator), 250);
// slash creator
assert_ok!(Pools::apply_slash(RawOrigin::Signed(slash_reporter).into(), creator));
// no pending slash anymore.
assert_eq!(Pools::api_member_pending_slash(creator), 0);
// all slash should be applied now.
assert_eq!(get_pool_agent(pool_id).ledger.pending_slash, 0);
assert_eq!(Pools::api_pool_pending_slash(pool_id), 0);
// for creator, 50% of stake should be slashed (250), 10% of which should go to reporter
// (25).
assert_eq!(Balances::free_balance(slash_reporter), 115 + 25);
});
}
#[test]
fn existing_pool_member_cannot_stake() {
// A pool member is able to stake directly since staking only uses free funds but once a
// staker, they cannot join/add extra bond to the pool. They can still withdraw funds.
ExtBuilder::default().build_and_execute(|| {
start_era(1);
// GIVEN: a pool.
fund(&200, 1000);
let pool_id = create_pool(200, 800);
// WHEN: delegator joins a pool
let delegator = 100;
fund(&delegator, 1000);
assert_ok!(Pools::join(RawOrigin::Signed(delegator).into(), 200, pool_id));
// THEN: they cannot stake anymore
assert_noop!(
Staking::bond(
RuntimeOrigin::signed(delegator),
500,
RewardDestination::Account(101)
),
StakingError::<T>::Restricted
);
});
}
#[test]
fn stakers_cannot_join_pool() {
ExtBuilder::default().build_and_execute(|| {
start_era(1);
// GIVEN: a pool.
fund(&200, 1000);
let pool_id = create_pool(200, 800);
// WHEN: an account is a staker.
let staker = 100;
fund(&staker, 1000);
assert_ok!(Staking::bond(
RuntimeOrigin::signed(staker),
500,
RewardDestination::Account(101)
));
assert_ok!(Staking::nominate(RuntimeOrigin::signed(staker), vec![GENESIS_VALIDATOR]));
// THEN: they cannot join pool.
assert_noop!(
Pools::join(RawOrigin::Signed(staker).into(), 200, pool_id),
PoolsError::<T>::Restricted
);
});
}
fn create_pool(creator: AccountId, amount: Balance) -> u32 {
fund(&creator, amount * 2);
assert_ok!(Pools::create(
RawOrigin::Signed(creator).into(),
amount,
creator,
creator,
creator
));
pezpallet_nomination_pools::LastPoolId::<T>::get()
}
fn add_delegators_to_pool(pool_id: u32, delegators: Vec<AccountId>, amount: Balance) {
for delegator in delegators {
fund(&delegator, amount * 2);
assert_ok!(Pools::join(RawOrigin::Signed(delegator).into(), amount, pool_id));
}
}
fn get_pool_agent(pool_id: u32) -> AgentLedgerOuter<T> {
get_agent_ledger(&Pools::generate_bonded_account(pool_id))
}
}