Lines
100 %
Functions
Branches
// Copyright (C) Moondance Labs Ltd.
// This file is part of Tanssi.
// Tanssi is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Tanssi is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Tanssi. If not, see <http://www.gnu.org/licenses/>
mod candidates;
mod delegator_flow;
mod manual_rewards;
mod rebalance;
mod rewards;
use {
crate::{
assert_eq_events, assert_fields_eq,
candidate::Candidates,
mock::*,
pool_test,
pools::{self, ActivePoolKind, Pool, PoolKind},
Error, Event, PendingOperationKey, PendingOperationQuery, PendingOperations, Shares,
SharesOrStake, Stake,
},
frame_support::{
assert_noop, assert_ok,
traits::tokens::fungible::{Balanced, Mutate},
sp_runtime::TokenError,
};
pub type Joining = pools::Joining<Runtime>;
pub type Leaving = pools::Leaving<Runtime>;
pub fn default<T: Default>() -> T {
Default::default()
}
pub(crate) fn operation_stake(
candidate: AccountId,
delegator: AccountId,
pool: ActivePoolKind,
at: u64,
) -> Balance {
let operation_key = match pool {
ActivePoolKind::AutoCompounding => {
PendingOperationKey::JoiningAutoCompounding { candidate, at }
ActivePoolKind::ManualRewards => {
PendingOperationKey::JoiningManualRewards { candidate, at }
let shares = PendingOperations::<Runtime>::get(delegator, operation_key);
if shares == 0 {
return 0;
Joining::shares_to_stake(&candidate, Shares(shares))
.unwrap()
.0
#[must_use]
pub(crate) struct RequestDelegation {
amount: Balance,
expected_joining: Balance,
impl RequestDelegation {
pub fn test(self) {
let Self {
candidate,
delegator,
pool,
amount,
expected_joining,
} = self;
let now = block_number();
let before = State::extract(candidate, delegator);
let pool_before = PoolState::extract::<Joining>(candidate, delegator);
let operation_before = operation_stake(candidate, delegator, pool, now);
assert_ok!(Staking::request_delegate(
RuntimeOrigin::signed(delegator),
));
let after = State::extract(candidate, delegator);
let pool_after = PoolState::extract::<Joining>(candidate, delegator);
let operation_after = operation_stake(candidate, delegator, pool, now);
// Actual balances don't change
assert_fields_eq!(before, after, [delegator_balance, staking_balance]);
assert_eq!(
before.delegator_hold + expected_joining,
after.delegator_hold
);
assert_eq!(pool_before.hold + expected_joining, pool_after.hold);
assert_eq!(pool_before.stake + expected_joining, pool_after.stake);
before.candidate_total_stake + expected_joining,
after.candidate_total_stake
assert_eq!(operation_before + expected_joining, operation_after);
#[derive(Default)]
pub(crate) struct ExecuteDelegation {
block_number: u64,
expected_increase: Balance,
expected_manual_rewards: Balance,
impl ExecuteDelegation {
pub fn test<P: PoolExt<Runtime>>(self) {
block_number,
expected_increase,
expected_manual_rewards,
let joining_before = PoolState::extract::<Joining>(candidate, delegator);
let pool_before = PoolState::extract::<P>(candidate, delegator);
let request_before = crate::PendingOperations::<Runtime>::get(
P::joining_operation_key(candidate, block_number),
let request_before =
pools::Joining::<Runtime>::shares_to_stake(&candidate, Shares(request_before))
.0;
let refund = request_before
.checked_sub(expected_increase)
.expect("expected increase should be <= requested amount");
assert_ok!(Staking::execute_pending_operations(
vec![PendingOperationQuery {
operation: P::joining_operation_key(candidate, block_number)
}]
let joining_after = PoolState::extract::<Joining>(candidate, delegator);
let pool_after = PoolState::extract::<P>(candidate, delegator);
let request_after = crate::PendingOperations::<Runtime>::get(
// Actual balances changes only due to manuyal rewards.
before.delegator_balance + expected_manual_rewards,
after.delegator_balance
before.staking_balance - expected_manual_rewards,
after.staking_balance
// However funds are held (with share rounding released)
assert_eq!(request_after, 0);
assert_eq!(before.delegator_hold - refund, after.delegator_hold);
before.candidate_total_stake - refund,
assert_eq!(joining_before.hold - request_before, joining_after.hold);
assert_eq!(joining_before.stake - request_before, joining_after.stake);
assert_eq!(pool_before.hold + expected_increase, pool_after.hold);
assert_eq!(pool_before.stake + expected_increase, pool_after.stake);
pub(crate) struct FullDelegation {
request_amount: Balance,
impl FullDelegation {
request_amount,
let block_number = block_number();
RequestDelegation {
pool: P::target_pool(),
amount: request_amount,
expected_joining: round_down(request_amount, 2),
.test();
roll_to(block_number + BLOCKS_TO_WAIT);
ExecuteDelegation {
.test::<P>();
pub(crate) struct RequestUndelegation {
request_amount: SharesOrStake<Balance>,
expected_removed: Balance,
expected_leaving: Balance,
expected_hold_rebalance: Balance,
impl Default for RequestUndelegation {
fn default() -> Self {
Self {
candidate: 0,
delegator: 0,
request_amount: SharesOrStake::Stake(0),
expected_removed: 0,
expected_leaving: 0,
expected_manual_rewards: 0,
expected_hold_rebalance: 0,
impl RequestUndelegation {
expected_removed,
expected_leaving,
expected_hold_rebalance,
let dust = expected_removed
.checked_sub(expected_leaving)
.expect("should removed >= leaving");
let leaving_before = PoolState::extract::<Leaving>(candidate, delegator);
assert_ok!(Staking::request_undelegate(
P::target_pool(),
let leaving_after = PoolState::extract::<Leaving>(candidate, delegator);
// Actual balances changes due to manual rewards and hold rebalance.
before.delegator_balance + expected_manual_rewards + expected_hold_rebalance,
before.staking_balance - expected_manual_rewards - expected_hold_rebalance,
// Dust is released immediately.
before.delegator_hold - dust + expected_hold_rebalance,
// Pool decrease.
assert_eq!(pool_before.stake - expected_removed, pool_after.stake);
pool_before.hold + expected_hold_rebalance - expected_removed,
pool_after.stake
// Leaving increase.
assert_eq!(leaving_before.stake + expected_leaving, leaving_after.stake);
assert_eq!(leaving_before.hold + expected_leaving, leaving_after.stake);
// Stake no longer contribute to election
before.candidate_total_stake - expected_removed,
pub(crate) struct ExecuteUndelegation {
expected_decrease: Balance,
impl ExecuteUndelegation {
expected_decrease,
operation: PendingOperationKey::Leaving {
at: block_number
let leaving_after = PoolState::extract::<Joining>(candidate, delegator);
PendingOperationKey::Leaving {
at: block_number,
before.delegator_hold - expected_decrease,
assert_fields_eq!(before, after, candidate_total_stake);
assert_eq!(leaving_before.hold - expected_decrease, leaving_after.hold);
leaving_before.stake - expected_decrease,
leaving_after.stake
pub(crate) struct FullUndelegation {
impl Default for FullUndelegation {
impl FullUndelegation {
RequestUndelegation {
..default()
ExecuteUndelegation {
expected_decrease: expected_leaving,
pub(crate) fn do_rebalance_hold<P: Pool<Runtime>>(
target_pool: PoolKind,
expected_rebalance: SignedBalance,
) {
assert_ok!(Staking::rebalance_hold(
RuntimeOrigin::signed(ACCOUNT_DELEGATOR_1),
ACCOUNT_CANDIDATE_1,
ACCOUNT_DELEGATOR_1,
target_pool
// Balances should update
match expected_rebalance {
SignedBalance::Positive(balance) => {
assert_eq!(pool_before.hold + balance, pool_after.hold);
assert_eq!(before.delegator_balance + balance, after.delegator_balance);
assert_eq!(before.staking_balance - balance, after.staking_balance);
SignedBalance::Negative(balance) => {
assert_eq!(pool_before.hold - balance, pool_after.hold);
assert_eq!(before.delegator_balance - balance, after.delegator_balance);
assert_eq!(before.staking_balance + balance, after.staking_balance);
// Stake stay the same.
assert_fields_eq!(pool_before, pool_after, stake);
pub(crate) fn currency_issue(amount: Balance) -> crate::CreditOf<Runtime> {
<<Runtime as crate::Config>::Currency as Balanced<AccountId>>::issue(amount)
pub(crate) struct Swap {
requested_amount: SharesOrStake<Balance>,
expected_restaked: Balance,
expected_released: Balance,
impl Default for Swap {
requested_amount: SharesOrStake::Stake(0),
expected_restaked: 0,
expected_released: 0,
impl Swap {
requested_amount,
expected_restaked,
expected_released,
let source_pool_before = PoolState::extract::<P>(candidate, delegator);
let target_pool_before = PoolState::extract::<P::OppositePool>(candidate, delegator);
assert_ok!(Staking::swap_pool(
requested_amount
let source_pool_after = PoolState::extract::<P>(candidate, delegator);
let target_pool_after = PoolState::extract::<P::OppositePool>(candidate, delegator);
// Actual balances changes due to hold rebalance.
before.delegator_balance + expected_hold_rebalance,
before.staking_balance - expected_hold_rebalance,
// Pool change.
source_pool_before.stake - expected_removed,
source_pool_after.stake
source_pool_before.hold + expected_hold_rebalance - expected_removed,
target_pool_before.stake + expected_restaked,
target_pool_after.stake
target_pool_before.hold + expected_restaked,
target_pool_after.hold
before.candidate_total_stake - expected_leaving - expected_released,
before.delegator_hold - expected_released + expected_hold_rebalance,