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/>
use {
super::*,
crate::{
assert_eq_last_events, CandidateSummaries, CandidateSummary, DelegatorCandidateSummaries,
DelegatorCandidateSummary, PausePoolsExtrinsics,
},
};
pool_test!(
fn empty_delegation<P>() {
ExtBuilder::default().build().execute_with(|| {
let before = State::extract(ACCOUNT_CANDIDATE_1, ACCOUNT_DELEGATOR_1);
let pool_before =
PoolState::extract::<Joining>(ACCOUNT_CANDIDATE_1, ACCOUNT_DELEGATOR_1);
assert_noop!(
Staking::request_delegate(
RuntimeOrigin::signed(ACCOUNT_DELEGATOR_1),
ACCOUNT_CANDIDATE_1,
P::target_pool(),
0
),
Error::<Runtime>::StakeMustBeNonZero
);
let after = State::extract(ACCOUNT_CANDIDATE_1, ACCOUNT_DELEGATOR_1);
let pool_after =
assert_eq!(before, after);
assert_eq!(pool_before, pool_after);
assert_eq_events!(Vec::<Event<Runtime>>::new());
})
}
fn delegation_request<P>() {
let amount = 3324;
RequestDelegation {
candidate: ACCOUNT_CANDIDATE_1,
delegator: ACCOUNT_DELEGATOR_1,
pool: P::target_pool(),
amount: amount + 1, // to test joining rounding
expected_joining: amount,
.test();
assert_eq!(
DelegatorCandidateSummaries::<Runtime>::iter_key_prefix(&ACCOUNT_DELEGATOR_1)
.count(),
1
DelegatorCandidateSummaries::<Runtime>::get(
&ACCOUNT_DELEGATOR_1,
&ACCOUNT_CANDIDATE_1
DelegatorCandidateSummary::new().with_joining(true)
CandidateSummaries::<Runtime>::get(&ACCOUNT_CANDIDATE_1),
CandidateSummary {
delegators: 1,
joining_delegators: 1,
..default()
assert_eq_events!(vec![
Event::IncreasedStake {
stake_diff: amount,
Event::UpdatedCandidatePosition {
stake: amount,
self_delegation: 0,
before: None,
after: None,
Event::RequestedDelegate {
pending: amount
]);
fn delegation_request_more_than_available<P>() {
let amount = DEFAULT_BALANCE; // not enough to keep ED
amount,
TokenError::FundsUnavailable
fn delegation_execution<P>() {
let final_amount = 2 * SHARE_INIT;
let requested_amount = final_amount + 10; // test share rounding
FullDelegation {
request_amount: requested_amount,
expected_increase: final_amount,
.test::<P>();
DelegatorCandidateSummary::new().with_pool(P::pool_kind(), true)
.with_pool(P::pool_kind(), 1)
stake_diff: requested_amount,
stake: requested_amount,
pending: requested_amount,
Event::DecreasedStake {
stake_diff: 10,
stake: final_amount,
P::event_staked(ACCOUNT_CANDIDATE_1, ACCOUNT_DELEGATOR_1, 2, final_amount),
Event::ExecutedDelegate {
staked: final_amount,
released: 10,
fn delegation_execution_too_soon<P>() {
let block_number = block_number();
amount: final_amount,
expected_joining: final_amount,
roll_to(block_number + BLOCKS_TO_WAIT - 1); // too soon
Staking::execute_pending_operations(
vec![PendingOperationQuery {
operation: P::joining_operation_key(ACCOUNT_CANDIDATE_1, block_number)
}]
Error::<Runtime>::RequestCannotBeExecuted(0)
fn undelegation_execution_too_soon<P>() {
let leaving_amount = round_down(final_amount, 3); // test leaving rounding
request_amount: final_amount,
RequestUndelegation {
request_amount: SharesOrStake::Stake(final_amount),
expected_removed: final_amount,
expected_leaving: leaving_amount,
operation: PendingOperationKey::Leaving {
at: block_number,
fn undelegation_execution<P>() {
assert_eq!(leaving_amount, 1_999_998);
FullUndelegation {
CandidateSummary::default(),
// delegate request
pending: requested_amount
// delegate exec
// undelegate request
stake_diff: final_amount,
stake: 0,
Event::RequestedUndelegate {
from: P::target_pool(),
pending: leaving_amount,
released: 2
// undelegate exec
Event::ExecutedUndelegate {
released: leaving_amount,
fn undelegation_execution_amount_in_shares<P>() {
let joining_amount = 2 * SHARE_INIT;
let joining_requested_amount = joining_amount + 10; // test share rounding
let leaving_requested_amount = SHARE_INIT;
let leaving_amount = round_down(leaving_requested_amount, 3); // test leaving rounding
assert_eq!(leaving_amount, 999_999);
request_amount: joining_requested_amount,
expected_increase: joining_amount,
request_amount: SharesOrStake::Shares(1),
expected_removed: leaving_requested_amount,
stake_diff: joining_requested_amount,
stake: joining_requested_amount,
pending: joining_requested_amount
stake: joining_amount,
P::event_staked(ACCOUNT_CANDIDATE_1, ACCOUNT_DELEGATOR_1, 2, joining_amount),
staked: joining_amount,
stake_diff: leaving_requested_amount,
stake: joining_amount - leaving_requested_amount,
released: 1
fn partial_swap_works<P>() {
request_amount: 10 * SHARE_INIT,
expected_increase: 10 * SHARE_INIT,
// We swap only part of the stake.
Swap {
requested_amount: SharesOrStake::Stake(5 * SHARE_INIT + 10),
expected_removed: 5 * SHARE_INIT,
expected_restaked: 5 * SHARE_INIT,
DelegatorCandidateSummary::new()
.with_pool(P::pool_kind(), true)
.with_pool(P::OppositePool::pool_kind(), true)
.with_pool(P::OppositePool::pool_kind(), 1)
assert_eq_last_events!(vec![Event::<Runtime>::SwappedPool {
source_pool: P::target_pool(),
source_shares: 5,
source_stake: 5 * SHARE_INIT,
target_shares: 5,
target_stake: 5 * SHARE_INIT,
pending_leaving: 0,
released: 0,
}]);
fn full_swap_works<P>() {
// All stake is swapped, so original pool should be empty
requested_amount: SharesOrStake::Stake(10 * SHARE_INIT),
expected_removed: 10 * SHARE_INIT,
expected_restaked: 10 * SHARE_INIT,
DelegatorCandidateSummary::new().with_pool(P::OppositePool::pool_kind(), true)
source_shares: 10,
source_stake: 10 * SHARE_INIT,
target_shares: 10,
target_stake: 10 * SHARE_INIT,
fn swap_too_much<P>() {
Staking::swap_pool(
SharesOrStake::Shares(11),
Error::<Runtime>::MathUnderflow
fn swap_with_rounding<P>() {
request_amount: 1 * SHARE_INIT,
expected_increase: 1 * SHARE_INIT,
.test::<P::OppositePool>();
// We then artificialy distribute rewards to the target by increasing the value of the pool
// and minting currency to the staking account (this is not how manual rewards would
// be distributed but whatever).
let rewards = 5 * KILO;
assert_ok!(Balances::mint_into(&ACCOUNT_STAKING, rewards));
assert_ok!(P::OppositePool::share_stake_among_holders(
&ACCOUNT_CANDIDATE_1,
Stake(rewards)
));
assert_ok!(Candidates::<Runtime>::add_total_stake(
&Stake(rewards)
// due to 1 target share now being worth a bit more than SHARE_INIT,
// only 4 target shares can be restaked
expected_restaked: 4_020_000,
// remaining amount is put in the leaving pool, rounded down
// to the closest multiple of 3 (test leaving share init value)
expected_leaving: 979_998,
// thus the 2 stake that could not be put in the leaving pool
// are directly released
expected_released: 2,
.with_leaving(true) // leaving dust
leaving_delegators: 1,
auto_compounding_delegators: 1,
manual_rewards_delegators: 1,
target_shares: 4,
target_stake: 4_020_000,
pending_leaving: 979_998,
released: 2,
#[test]
fn paused_extrinsics() {
PausePoolsExtrinsics::<Runtime>::put(true);
Staking::rebalance_hold(
ACCOUNT_DELEGATOR_1,
PoolKind::AutoCompounding
Error::<Runtime>::NoOneIsStaking
); // no pause check
ActivePoolKind::AutoCompounding,
42
Error::<Runtime>::PoolsExtrinsicsArePaused
Staking::execute_pending_operations(RuntimeOrigin::signed(ACCOUNT_DELEGATOR_1), vec![]),
Staking::request_undelegate(
SharesOrStake::Shares(42),
assert_ok!(Staking::claim_manual_rewards(
vec![]
assert_ok!(Staking::update_candidate_position(