Files
pezkuwi-subxt/cumulus/pallets/collator-selection/src/tests.rs
T
georgepisaltu 7d735fc8ae Add simple collator election mechanism (#1340)
Fixes https://github.com/paritytech/polkadot-sdk/issues/106

Port of cumulus PR https://github.com/paritytech/cumulus/pull/2960

This PR adds the ability to bid for collator slots even after the max
number of collators have already registered. This eliminates the first
come, first served mechanism that was in place before.

Key changes:
- added `update_bond` extrinsic to allow registered candidates to adjust
their bonds in order to dynamically control their bids
- added `take_candidate_slot` extrinsic to try to replace an already
existing candidate by bidding more than them
- candidates are now kept in a sorted list in the pallet storage, where
the top `DesiredCandidates` out of `MaxCandidates` candidates in the
list will be selected by the session pallet as collators
- if the candidacy bond is increased through a `set_candidacy_bond`
call, candidates which don't meet the new bond requirements are kicked


# Checklist

- [ ] My PR includes a detailed description as outlined in the
"Description" section above
- [ ] My PR follows the [labeling
requirements](https://github.com/paritytech/polkadot-sdk/blob/master/docs/CONTRIBUTING.md#process)
of this project (at minimum one label for `T` required)
- [ ] I have made corresponding changes to the documentation (if
applicable)
- [ ] I have added tests that prove my fix is effective or that my
feature works (if applicable)
- [ ] If this PR alters any external APIs or interfaces used by
Polkadot, the corresponding Polkadot PR is ready as well as the
corresponding Cumulus PR (optional)

---------

Signed-off-by: georgepisaltu <george.pisaltu@parity.io>
2023-11-14 18:22:30 +02:00

1595 lines
54 KiB
Rust

// 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.
use crate as collator_selection;
use crate::{mock::*, CandidateInfo, Error};
use frame_support::{
assert_noop, assert_ok,
traits::{Currency, OnInitialize},
};
use pallet_balances::Error as BalancesError;
use sp_runtime::{testing::UintAuthorityId, traits::BadOrigin, BuildStorage};
#[test]
fn basic_setup_works() {
new_test_ext().execute_with(|| {
assert_eq!(CollatorSelection::desired_candidates(), 2);
assert_eq!(CollatorSelection::candidacy_bond(), 10);
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 0);
// genesis should sort input
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
});
}
#[test]
fn it_should_set_invulnerables() {
new_test_ext().execute_with(|| {
let mut new_set = vec![1, 4, 3, 2];
assert_ok!(CollatorSelection::set_invulnerables(
RuntimeOrigin::signed(RootAccount::get()),
new_set.clone()
));
new_set.sort();
assert_eq!(CollatorSelection::invulnerables(), new_set);
// cannot set with non-root.
assert_noop!(
CollatorSelection::set_invulnerables(RuntimeOrigin::signed(1), new_set),
BadOrigin
);
});
}
#[test]
fn it_should_set_invulnerables_even_with_some_invalid() {
new_test_ext().execute_with(|| {
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
let new_with_invalid = vec![1, 4, 3, 42, 2];
assert_ok!(CollatorSelection::set_invulnerables(
RuntimeOrigin::signed(RootAccount::get()),
new_with_invalid
));
// should succeed and order them, but not include 42
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2, 3, 4]);
});
}
#[test]
fn add_invulnerable_works() {
new_test_ext().execute_with(|| {
initialize_to_block(1);
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
let new = 3;
// function runs
assert_ok!(CollatorSelection::add_invulnerable(
RuntimeOrigin::signed(RootAccount::get()),
new
));
System::assert_last_event(RuntimeEvent::CollatorSelection(
crate::Event::InvulnerableAdded { account_id: new },
));
// same element cannot be added more than once
assert_noop!(
CollatorSelection::add_invulnerable(RuntimeOrigin::signed(RootAccount::get()), new),
Error::<Test>::AlreadyInvulnerable
);
// new element is now part of the invulnerables list
assert!(CollatorSelection::invulnerables().to_vec().contains(&new));
// cannot add with non-root
assert_noop!(CollatorSelection::add_invulnerable(RuntimeOrigin::signed(1), new), BadOrigin);
// cannot add invulnerable without associated validator keys
let not_validator = 42;
assert_noop!(
CollatorSelection::add_invulnerable(
RuntimeOrigin::signed(RootAccount::get()),
not_validator
),
Error::<Test>::ValidatorNotRegistered
);
});
}
#[test]
fn invulnerable_limit_works() {
new_test_ext().execute_with(|| {
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
// MaxInvulnerables: u32 = 20
for ii in 3..=21 {
// only keys were registered in mock for 1 to 5
if ii > 5 {
Balances::make_free_balance_be(&ii, 100);
let key = MockSessionKeys { aura: UintAuthorityId(ii) };
Session::set_keys(RuntimeOrigin::signed(ii).into(), key, Vec::new()).unwrap();
}
assert_eq!(Balances::free_balance(ii), 100);
if ii < 21 {
assert_ok!(CollatorSelection::add_invulnerable(
RuntimeOrigin::signed(RootAccount::get()),
ii
));
} else {
assert_noop!(
CollatorSelection::add_invulnerable(
RuntimeOrigin::signed(RootAccount::get()),
ii
),
Error::<Test>::TooManyInvulnerables
);
}
}
let expected: Vec<u64> = (1..=20).collect();
assert_eq!(CollatorSelection::invulnerables(), expected);
// Cannot set too many Invulnerables
let too_many_invulnerables: Vec<u64> = (1..=21).collect();
assert_noop!(
CollatorSelection::set_invulnerables(
RuntimeOrigin::signed(RootAccount::get()),
too_many_invulnerables
),
Error::<Test>::TooManyInvulnerables
);
assert_eq!(CollatorSelection::invulnerables(), expected);
});
}
#[test]
fn remove_invulnerable_works() {
new_test_ext().execute_with(|| {
initialize_to_block(1);
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
assert_ok!(CollatorSelection::add_invulnerable(
RuntimeOrigin::signed(RootAccount::get()),
4
));
assert_ok!(CollatorSelection::add_invulnerable(
RuntimeOrigin::signed(RootAccount::get()),
3
));
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2, 3, 4]);
assert_ok!(CollatorSelection::remove_invulnerable(
RuntimeOrigin::signed(RootAccount::get()),
2
));
System::assert_last_event(RuntimeEvent::CollatorSelection(
crate::Event::InvulnerableRemoved { account_id: 2 },
));
assert_eq!(CollatorSelection::invulnerables(), vec![1, 3, 4]);
// cannot remove invulnerable not in the list
assert_noop!(
CollatorSelection::remove_invulnerable(RuntimeOrigin::signed(RootAccount::get()), 2),
Error::<Test>::NotInvulnerable
);
// cannot remove without privilege
assert_noop!(
CollatorSelection::remove_invulnerable(RuntimeOrigin::signed(1), 3),
BadOrigin
);
});
}
#[test]
fn candidate_to_invulnerable_works() {
new_test_ext().execute_with(|| {
initialize_to_block(1);
assert_eq!(CollatorSelection::desired_candidates(), 2);
assert_eq!(CollatorSelection::candidacy_bond(), 10);
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 0);
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
assert_eq!(Balances::free_balance(3), 100);
assert_eq!(Balances::free_balance(4), 100);
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
assert_eq!(Balances::free_balance(3), 90);
assert_eq!(Balances::free_balance(4), 90);
assert_ok!(CollatorSelection::add_invulnerable(
RuntimeOrigin::signed(RootAccount::get()),
3
));
System::assert_has_event(RuntimeEvent::CollatorSelection(crate::Event::CandidateRemoved {
account_id: 3,
}));
System::assert_has_event(RuntimeEvent::CollatorSelection(
crate::Event::InvulnerableAdded { account_id: 3 },
));
assert!(CollatorSelection::invulnerables().to_vec().contains(&3));
assert_eq!(Balances::free_balance(3), 100);
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 1);
assert_ok!(CollatorSelection::add_invulnerable(
RuntimeOrigin::signed(RootAccount::get()),
4
));
System::assert_has_event(RuntimeEvent::CollatorSelection(crate::Event::CandidateRemoved {
account_id: 4,
}));
System::assert_has_event(RuntimeEvent::CollatorSelection(
crate::Event::InvulnerableAdded { account_id: 4 },
));
assert!(CollatorSelection::invulnerables().to_vec().contains(&4));
assert_eq!(Balances::free_balance(4), 100);
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 0);
});
}
#[test]
fn set_desired_candidates_works() {
new_test_ext().execute_with(|| {
// given
assert_eq!(CollatorSelection::desired_candidates(), 2);
// can set
assert_ok!(CollatorSelection::set_desired_candidates(
RuntimeOrigin::signed(RootAccount::get()),
7
));
assert_eq!(CollatorSelection::desired_candidates(), 7);
// rejects bad origin
assert_noop!(
CollatorSelection::set_desired_candidates(RuntimeOrigin::signed(1), 8),
BadOrigin
);
});
}
#[test]
fn set_candidacy_bond_empty_candidate_list() {
new_test_ext().execute_with(|| {
// given
assert_eq!(CollatorSelection::candidacy_bond(), 10);
assert!(<crate::CandidateList<Test>>::get().is_empty());
// can decrease without candidates
assert_ok!(CollatorSelection::set_candidacy_bond(
RuntimeOrigin::signed(RootAccount::get()),
7
));
assert_eq!(CollatorSelection::candidacy_bond(), 7);
assert!(<crate::CandidateList<Test>>::get().is_empty());
// rejects bad origin.
assert_noop!(CollatorSelection::set_candidacy_bond(RuntimeOrigin::signed(1), 8), BadOrigin);
// can increase without candidates
assert_ok!(CollatorSelection::set_candidacy_bond(
RuntimeOrigin::signed(RootAccount::get()),
20
));
assert!(<crate::CandidateList<Test>>::get().is_empty());
assert_eq!(CollatorSelection::candidacy_bond(), 20);
});
}
#[test]
fn set_candidacy_bond_with_one_candidate() {
new_test_ext().execute_with(|| {
// given
assert_eq!(CollatorSelection::candidacy_bond(), 10);
assert!(<crate::CandidateList<Test>>::get().is_empty());
let candidate_3 = CandidateInfo { who: 3, deposit: 10 };
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_eq!(<crate::CandidateList<Test>>::get(), vec![candidate_3.clone()]);
// can decrease with one candidate
assert_ok!(CollatorSelection::set_candidacy_bond(
RuntimeOrigin::signed(RootAccount::get()),
7
));
assert_eq!(CollatorSelection::candidacy_bond(), 7);
assert_eq!(<crate::CandidateList<Test>>::get(), vec![candidate_3.clone()]);
// can increase up to initial deposit
assert_ok!(CollatorSelection::set_candidacy_bond(
RuntimeOrigin::signed(RootAccount::get()),
10
));
assert_eq!(CollatorSelection::candidacy_bond(), 10);
assert_eq!(<crate::CandidateList<Test>>::get(), vec![candidate_3.clone()]);
// can increase past initial deposit, should kick existing candidate
assert_ok!(CollatorSelection::set_candidacy_bond(
RuntimeOrigin::signed(RootAccount::get()),
20
));
assert!(<crate::CandidateList<Test>>::get().is_empty());
});
}
#[test]
fn set_candidacy_bond_with_many_candidates_same_deposit() {
new_test_ext().execute_with(|| {
// given
assert_eq!(CollatorSelection::candidacy_bond(), 10);
assert!(<crate::CandidateList<Test>>::get().is_empty());
let candidate_3 = CandidateInfo { who: 3, deposit: 10 };
let candidate_4 = CandidateInfo { who: 4, deposit: 10 };
let candidate_5 = CandidateInfo { who: 5, deposit: 10 };
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5)));
assert_eq!(
<crate::CandidateList<Test>>::get(),
vec![candidate_5.clone(), candidate_4.clone(), candidate_3.clone()]
);
// can decrease with multiple candidates
assert_ok!(CollatorSelection::set_candidacy_bond(
RuntimeOrigin::signed(RootAccount::get()),
7
));
assert_eq!(CollatorSelection::candidacy_bond(), 7);
assert_eq!(
<crate::CandidateList<Test>>::get(),
vec![candidate_5.clone(), candidate_4.clone(), candidate_3.clone()]
);
// can increase up to initial deposit
assert_ok!(CollatorSelection::set_candidacy_bond(
RuntimeOrigin::signed(RootAccount::get()),
10
));
assert_eq!(CollatorSelection::candidacy_bond(), 10);
assert_eq!(
<crate::CandidateList<Test>>::get(),
vec![candidate_5.clone(), candidate_4.clone(), candidate_3.clone()]
);
// can increase past initial deposit, should kick existing candidates
assert_ok!(CollatorSelection::set_candidacy_bond(
RuntimeOrigin::signed(RootAccount::get()),
20
));
assert!(<crate::CandidateList<Test>>::get().is_empty());
});
}
#[test]
fn set_candidacy_bond_with_many_candidates_different_deposits() {
new_test_ext().execute_with(|| {
// given
assert_eq!(CollatorSelection::candidacy_bond(), 10);
assert!(<crate::CandidateList<Test>>::get().is_empty());
let candidate_3 = CandidateInfo { who: 3, deposit: 10 };
let candidate_4 = CandidateInfo { who: 4, deposit: 20 };
let candidate_5 = CandidateInfo { who: 5, deposit: 30 };
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5)));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 30));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 20));
assert_eq!(
<crate::CandidateList<Test>>::get(),
vec![candidate_3.clone(), candidate_4.clone(), candidate_5.clone()]
);
// can decrease with multiple candidates
assert_ok!(CollatorSelection::set_candidacy_bond(
RuntimeOrigin::signed(RootAccount::get()),
7
));
assert_eq!(CollatorSelection::candidacy_bond(), 7);
assert_eq!(
<crate::CandidateList<Test>>::get(),
vec![candidate_3.clone(), candidate_4.clone(), candidate_5.clone()]
);
// can increase up to initial deposit
assert_ok!(CollatorSelection::set_candidacy_bond(
RuntimeOrigin::signed(RootAccount::get()),
10
));
assert_eq!(CollatorSelection::candidacy_bond(), 10);
assert_eq!(
<crate::CandidateList<Test>>::get(),
vec![candidate_3.clone(), candidate_4.clone(), candidate_5.clone()]
);
// can increase to 4's deposit, should kick 3
assert_ok!(CollatorSelection::set_candidacy_bond(
RuntimeOrigin::signed(RootAccount::get()),
20
));
assert_eq!(CollatorSelection::candidacy_bond(), 20);
assert_eq!(
<crate::CandidateList<Test>>::get(),
vec![candidate_4.clone(), candidate_5.clone()]
);
// can increase past 4's deposit, should kick 4
assert_ok!(CollatorSelection::set_candidacy_bond(
RuntimeOrigin::signed(RootAccount::get()),
25
));
assert_eq!(CollatorSelection::candidacy_bond(), 25);
assert_eq!(<crate::CandidateList<Test>>::get(), vec![candidate_5.clone()]);
// lowering the minimum deposit should have no effect
assert_ok!(CollatorSelection::set_candidacy_bond(
RuntimeOrigin::signed(RootAccount::get()),
5
));
assert_eq!(CollatorSelection::candidacy_bond(), 5);
assert_eq!(<crate::CandidateList<Test>>::get(), vec![candidate_5.clone()]);
// add 3 and 4 back but with higher deposits than minimum
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 10));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 20));
assert_eq!(
<crate::CandidateList<Test>>::get(),
vec![candidate_3.clone(), candidate_4.clone(), candidate_5.clone()]
);
// can increase the deposit above the current max in the list, all candidates should be
// kicked
assert_ok!(CollatorSelection::set_candidacy_bond(
RuntimeOrigin::signed(RootAccount::get()),
40
));
assert_eq!(CollatorSelection::candidacy_bond(), 40);
assert!(<crate::CandidateList<Test>>::get().is_empty());
});
}
#[test]
fn cannot_register_candidate_if_too_many() {
new_test_ext().execute_with(|| {
<crate::DesiredCandidates<Test>>::put(1);
// MaxCandidates: u32 = 20
// Aside from 3, 4, and 5, create enough accounts to have 21 potential
// candidates.
for i in 6..=23 {
Balances::make_free_balance_be(&i, 100);
let key = MockSessionKeys { aura: UintAuthorityId(i) };
Session::set_keys(RuntimeOrigin::signed(i).into(), key, Vec::new()).unwrap();
}
for c in 3..=22 {
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(c)));
}
assert_noop!(
CollatorSelection::register_as_candidate(RuntimeOrigin::signed(23)),
Error::<Test>::TooManyCandidates,
);
})
}
#[test]
fn cannot_unregister_candidate_if_too_few() {
new_test_ext().execute_with(|| {
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 0);
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
assert_ok!(CollatorSelection::remove_invulnerable(
RuntimeOrigin::signed(RootAccount::get()),
1
));
assert_noop!(
CollatorSelection::remove_invulnerable(RuntimeOrigin::signed(RootAccount::get()), 2),
Error::<Test>::TooFewEligibleCollators,
);
// reset desired candidates:
<crate::DesiredCandidates<Test>>::put(1);
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
// now we can remove `2`
assert_ok!(CollatorSelection::remove_invulnerable(
RuntimeOrigin::signed(RootAccount::get()),
2
));
// can not remove too few
assert_noop!(
CollatorSelection::leave_intent(RuntimeOrigin::signed(4)),
Error::<Test>::TooFewEligibleCollators,
);
})
}
#[test]
fn cannot_register_as_candidate_if_invulnerable() {
new_test_ext().execute_with(|| {
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
// can't 1 because it is invulnerable.
assert_noop!(
CollatorSelection::register_as_candidate(RuntimeOrigin::signed(1)),
Error::<Test>::AlreadyInvulnerable,
);
})
}
#[test]
fn cannot_register_as_candidate_if_keys_not_registered() {
new_test_ext().execute_with(|| {
// can't 7 because keys not registered.
assert_noop!(
CollatorSelection::register_as_candidate(RuntimeOrigin::signed(42)),
Error::<Test>::ValidatorNotRegistered
);
})
}
#[test]
fn cannot_register_dupe_candidate() {
new_test_ext().execute_with(|| {
// can add 3 as candidate
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
// tuple of (id, deposit).
let addition = CandidateInfo { who: 3, deposit: 10 };
assert_eq!(
<crate::CandidateList<Test>>::get().iter().cloned().collect::<Vec<_>>(),
vec![addition]
);
assert_eq!(CollatorSelection::last_authored_block(3), 10);
assert_eq!(Balances::free_balance(3), 90);
// but no more
assert_noop!(
CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)),
Error::<Test>::AlreadyCandidate,
);
})
}
#[test]
fn cannot_register_as_candidate_if_poor() {
new_test_ext().execute_with(|| {
assert_eq!(Balances::free_balance(3), 100);
assert_eq!(Balances::free_balance(33), 0);
// works
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
// poor
assert_noop!(
CollatorSelection::register_as_candidate(RuntimeOrigin::signed(33)),
BalancesError::<Test>::InsufficientBalance,
);
});
}
#[test]
fn register_as_candidate_works() {
new_test_ext().execute_with(|| {
// given
assert_eq!(CollatorSelection::desired_candidates(), 2);
assert_eq!(CollatorSelection::candidacy_bond(), 10);
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 0);
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
// take two endowed, non-invulnerables accounts.
assert_eq!(Balances::free_balance(3), 100);
assert_eq!(Balances::free_balance(4), 100);
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
assert_eq!(Balances::free_balance(3), 90);
assert_eq!(Balances::free_balance(4), 90);
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 2);
});
}
#[test]
fn cannot_take_candidate_slot_if_invulnerable() {
new_test_ext().execute_with(|| {
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
// can't 1 because it is invulnerable.
assert_noop!(
CollatorSelection::take_candidate_slot(RuntimeOrigin::signed(1), 50u64.into(), 2),
Error::<Test>::AlreadyInvulnerable,
);
})
}
#[test]
fn cannot_take_candidate_slot_if_keys_not_registered() {
new_test_ext().execute_with(|| {
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_noop!(
CollatorSelection::take_candidate_slot(RuntimeOrigin::signed(42), 50u64.into(), 3),
Error::<Test>::ValidatorNotRegistered
);
})
}
#[test]
fn cannot_take_candidate_slot_if_duplicate() {
new_test_ext().execute_with(|| {
// can add 3 as candidate
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
// tuple of (id, deposit).
let candidate_3 = CandidateInfo { who: 3, deposit: 10 };
let candidate_4 = CandidateInfo { who: 4, deposit: 10 };
let actual_candidates =
<crate::CandidateList<Test>>::get().iter().cloned().collect::<Vec<_>>();
assert_eq!(actual_candidates, vec![candidate_4, candidate_3]);
assert_eq!(CollatorSelection::last_authored_block(3), 10);
assert_eq!(CollatorSelection::last_authored_block(4), 10);
assert_eq!(Balances::free_balance(3), 90);
// but no more
assert_noop!(
CollatorSelection::take_candidate_slot(RuntimeOrigin::signed(3), 50u64.into(), 4),
Error::<Test>::AlreadyCandidate,
);
})
}
#[test]
fn cannot_take_candidate_slot_if_target_invalid() {
new_test_ext().execute_with(|| {
// can add 3 as candidate
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
// tuple of (id, deposit).
let candidate_3 = CandidateInfo { who: 3, deposit: 10 };
assert_eq!(
<crate::CandidateList<Test>>::get().iter().cloned().collect::<Vec<_>>(),
vec![candidate_3]
);
assert_eq!(CollatorSelection::last_authored_block(3), 10);
assert_eq!(Balances::free_balance(3), 90);
assert_eq!(Balances::free_balance(4), 100);
assert_noop!(
CollatorSelection::take_candidate_slot(RuntimeOrigin::signed(4), 50u64.into(), 5),
Error::<Test>::TargetIsNotCandidate,
);
})
}
#[test]
fn cannot_take_candidate_slot_if_poor() {
new_test_ext().execute_with(|| {
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
assert_eq!(Balances::free_balance(3), 100);
assert_eq!(Balances::free_balance(33), 0);
// works
assert_ok!(CollatorSelection::take_candidate_slot(
RuntimeOrigin::signed(3),
20u64.into(),
4
));
// poor
assert_noop!(
CollatorSelection::take_candidate_slot(RuntimeOrigin::signed(33), 30u64.into(), 3),
BalancesError::<Test>::InsufficientBalance,
);
});
}
#[test]
fn cannot_take_candidate_slot_if_insufficient_deposit() {
new_test_ext().execute_with(|| {
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 60u64.into()));
assert_eq!(Balances::free_balance(3), 40);
assert_eq!(Balances::free_balance(4), 100);
assert_noop!(
CollatorSelection::take_candidate_slot(RuntimeOrigin::signed(4), 5u64.into(), 3),
Error::<Test>::InsufficientBond,
);
assert_eq!(Balances::free_balance(3), 40);
assert_eq!(Balances::free_balance(4), 100);
});
}
#[test]
fn cannot_take_candidate_slot_if_deposit_less_than_target() {
new_test_ext().execute_with(|| {
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 60u64.into()));
assert_eq!(Balances::free_balance(3), 40);
assert_eq!(Balances::free_balance(4), 100);
assert_noop!(
CollatorSelection::take_candidate_slot(RuntimeOrigin::signed(4), 20u64.into(), 3),
Error::<Test>::InsufficientBond,
);
assert_eq!(Balances::free_balance(3), 40);
assert_eq!(Balances::free_balance(4), 100);
});
}
#[test]
fn take_candidate_slot_works() {
new_test_ext().execute_with(|| {
// given
assert_eq!(CollatorSelection::desired_candidates(), 2);
assert_eq!(CollatorSelection::candidacy_bond(), 10);
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 0);
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
// take two endowed, non-invulnerables accounts.
assert_eq!(Balances::free_balance(3), 100);
assert_eq!(Balances::free_balance(4), 100);
assert_eq!(Balances::free_balance(5), 100);
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5)));
assert_eq!(Balances::free_balance(3), 90);
assert_eq!(Balances::free_balance(4), 90);
assert_eq!(Balances::free_balance(5), 90);
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 3);
Balances::make_free_balance_be(&6, 100);
let key = MockSessionKeys { aura: UintAuthorityId(6) };
Session::set_keys(RuntimeOrigin::signed(6).into(), key, Vec::new()).unwrap();
assert_ok!(CollatorSelection::take_candidate_slot(
RuntimeOrigin::signed(6),
50u64.into(),
4
));
assert_eq!(Balances::free_balance(3), 90);
assert_eq!(Balances::free_balance(4), 100);
assert_eq!(Balances::free_balance(5), 90);
assert_eq!(Balances::free_balance(6), 50);
// tuple of (id, deposit).
let candidate_3 = CandidateInfo { who: 3, deposit: 10 };
let candidate_6 = CandidateInfo { who: 6, deposit: 50 };
let candidate_5 = CandidateInfo { who: 5, deposit: 10 };
let mut actual_candidates =
<crate::CandidateList<Test>>::get().iter().cloned().collect::<Vec<_>>();
actual_candidates.sort_by(|info_1, info_2| info_1.deposit.cmp(&info_2.deposit));
assert_eq!(
<crate::CandidateList<Test>>::get().iter().cloned().collect::<Vec<_>>(),
vec![candidate_5, candidate_3, candidate_6]
);
});
}
#[test]
fn increase_candidacy_bond_non_candidate_account() {
new_test_ext().execute_with(|| {
// given
assert_eq!(CollatorSelection::desired_candidates(), 2);
assert_eq!(CollatorSelection::candidacy_bond(), 10);
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 0);
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
assert_noop!(
CollatorSelection::update_bond(RuntimeOrigin::signed(5), 20),
Error::<Test>::NotCandidate
);
});
}
#[test]
fn increase_candidacy_bond_insufficient_balance() {
new_test_ext().execute_with(|| {
// given
assert_eq!(CollatorSelection::desired_candidates(), 2);
assert_eq!(CollatorSelection::candidacy_bond(), 10);
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 0);
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
// take two endowed, non-invulnerables accounts.
assert_eq!(Balances::free_balance(3), 100);
assert_eq!(Balances::free_balance(4), 100);
assert_eq!(Balances::free_balance(5), 100);
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5)));
assert_eq!(Balances::free_balance(3), 90);
assert_eq!(Balances::free_balance(4), 90);
assert_eq!(Balances::free_balance(5), 90);
assert_noop!(
CollatorSelection::update_bond(RuntimeOrigin::signed(3), 110),
BalancesError::<Test>::InsufficientBalance
);
assert_eq!(Balances::free_balance(3), 90);
});
}
#[test]
fn increase_candidacy_bond_works() {
new_test_ext().execute_with(|| {
// given
assert_eq!(CollatorSelection::desired_candidates(), 2);
assert_eq!(CollatorSelection::candidacy_bond(), 10);
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 0);
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
// take three endowed, non-invulnerables accounts.
assert_eq!(Balances::free_balance(3), 100);
assert_eq!(Balances::free_balance(4), 100);
assert_eq!(Balances::free_balance(5), 100);
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5)));
assert_eq!(Balances::free_balance(3), 90);
assert_eq!(Balances::free_balance(4), 90);
assert_eq!(Balances::free_balance(5), 90);
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 20));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 30));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 40));
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 3);
assert_eq!(Balances::free_balance(3), 80);
assert_eq!(Balances::free_balance(4), 70);
assert_eq!(Balances::free_balance(5), 60);
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 40));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 60));
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 3);
assert_eq!(Balances::free_balance(3), 60);
assert_eq!(Balances::free_balance(4), 40);
assert_eq!(Balances::free_balance(5), 60);
});
}
#[test]
fn decrease_candidacy_bond_non_candidate_account() {
new_test_ext().execute_with(|| {
// given
assert_eq!(CollatorSelection::desired_candidates(), 2);
assert_eq!(CollatorSelection::candidacy_bond(), 10);
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 0);
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
assert_eq!(Balances::free_balance(5), 100);
assert_noop!(
CollatorSelection::update_bond(RuntimeOrigin::signed(5), 10),
Error::<Test>::NotCandidate
);
assert_eq!(Balances::free_balance(5), 100);
});
}
#[test]
fn decrease_candidacy_bond_insufficient_funds() {
new_test_ext().execute_with(|| {
// given
assert_eq!(CollatorSelection::desired_candidates(), 2);
assert_eq!(CollatorSelection::candidacy_bond(), 10);
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 0);
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
// take two endowed, non-invulnerables accounts.
assert_eq!(Balances::free_balance(3), 100);
assert_eq!(Balances::free_balance(4), 100);
assert_eq!(Balances::free_balance(5), 100);
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5)));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 60));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 60));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 60));
assert_eq!(Balances::free_balance(3), 40);
assert_eq!(Balances::free_balance(4), 40);
assert_eq!(Balances::free_balance(5), 40);
assert_noop!(
CollatorSelection::update_bond(RuntimeOrigin::signed(3), 0),
Error::<Test>::DepositTooLow
);
assert_noop!(
CollatorSelection::update_bond(RuntimeOrigin::signed(4), 5),
Error::<Test>::DepositTooLow
);
assert_noop!(
CollatorSelection::update_bond(RuntimeOrigin::signed(5), 9),
Error::<Test>::DepositTooLow
);
assert_eq!(Balances::free_balance(3), 40);
assert_eq!(Balances::free_balance(4), 40);
assert_eq!(Balances::free_balance(5), 40);
});
}
#[test]
fn decrease_candidacy_bond_occupying_top_slot() {
new_test_ext().execute_with(|| {
assert_eq!(CollatorSelection::desired_candidates(), 2);
// Register 3 candidates.
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5)));
// And update their bids.
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 30u64.into()));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 30u64.into()));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 60u64.into()));
// tuple of (id, deposit).
let candidate_3 = CandidateInfo { who: 3, deposit: 30 };
let candidate_4 = CandidateInfo { who: 4, deposit: 30 };
let candidate_5 = CandidateInfo { who: 5, deposit: 60 };
assert_eq!(
<crate::CandidateList<Test>>::get().iter().cloned().collect::<Vec<_>>(),
vec![candidate_4, candidate_3, candidate_5]
);
// Candidates 5 and 3 can't decrease their deposits because they are the 2 top candidates.
assert_noop!(
CollatorSelection::update_bond(RuntimeOrigin::signed(5), 29),
Error::<Test>::InvalidUnreserve,
);
assert_noop!(
CollatorSelection::update_bond(RuntimeOrigin::signed(3), 29),
Error::<Test>::InvalidUnreserve,
);
// But candidate 4 should have be able to decrease the deposit up to the minimum.
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 29u64.into()));
// Make candidate 4 outbid candidate 3, taking their spot as the second highest bid.
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 35u64.into()));
// tuple of (id, deposit).
let candidate_3 = CandidateInfo { who: 3, deposit: 30 };
let candidate_4 = CandidateInfo { who: 4, deposit: 35 };
let candidate_5 = CandidateInfo { who: 5, deposit: 60 };
assert_eq!(
<crate::CandidateList<Test>>::get().iter().cloned().collect::<Vec<_>>(),
vec![candidate_3, candidate_4, candidate_5]
);
// Now candidates 5 and 4 are the 2 top candidates, so they can't decrease their deposits.
assert_noop!(
CollatorSelection::update_bond(RuntimeOrigin::signed(5), 34),
Error::<Test>::InvalidUnreserve,
);
assert_noop!(
CollatorSelection::update_bond(RuntimeOrigin::signed(4), 34),
Error::<Test>::InvalidUnreserve,
);
// Candidate 3 should have be able to decrease the deposit up to the minimum now that
// they've fallen out of the top spots.
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 10u64.into()));
});
}
#[test]
fn decrease_candidacy_bond_works() {
new_test_ext().execute_with(|| {
// given
assert_eq!(CollatorSelection::desired_candidates(), 2);
assert_eq!(CollatorSelection::candidacy_bond(), 10);
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 0);
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
// take three endowed, non-invulnerables accounts.
assert_eq!(Balances::free_balance(3), 100);
assert_eq!(Balances::free_balance(4), 100);
assert_eq!(Balances::free_balance(5), 100);
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5)));
assert_eq!(Balances::free_balance(3), 90);
assert_eq!(Balances::free_balance(4), 90);
assert_eq!(Balances::free_balance(5), 90);
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 20));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 30));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 40));
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 3);
assert_eq!(Balances::free_balance(3), 80);
assert_eq!(Balances::free_balance(4), 70);
assert_eq!(Balances::free_balance(5), 60);
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 10));
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 3);
assert_eq!(Balances::free_balance(3), 90);
assert_eq!(Balances::free_balance(4), 70);
assert_eq!(Balances::free_balance(5), 60);
});
}
#[test]
fn update_candidacy_bond_with_identical_amount() {
new_test_ext().execute_with(|| {
// given
assert_eq!(CollatorSelection::desired_candidates(), 2);
assert_eq!(CollatorSelection::candidacy_bond(), 10);
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 0);
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
// take three endowed, non-invulnerables accounts.
assert_eq!(Balances::free_balance(3), 100);
assert_eq!(Balances::free_balance(4), 100);
assert_eq!(Balances::free_balance(5), 100);
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5)));
assert_eq!(Balances::free_balance(3), 90);
assert_eq!(Balances::free_balance(4), 90);
assert_eq!(Balances::free_balance(5), 90);
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 20));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 30));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 40));
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 3);
assert_eq!(Balances::free_balance(3), 80);
assert_eq!(Balances::free_balance(4), 70);
assert_eq!(Balances::free_balance(5), 60);
assert_noop!(
CollatorSelection::update_bond(RuntimeOrigin::signed(3), 20),
Error::<Test>::IdenticalDeposit
);
assert_eq!(Balances::free_balance(3), 80);
});
}
#[test]
fn candidate_list_works() {
new_test_ext().execute_with(|| {
// given
assert_eq!(CollatorSelection::desired_candidates(), 2);
assert_eq!(CollatorSelection::candidacy_bond(), 10);
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 0);
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
// take three endowed, non-invulnerables accounts.
assert_eq!(Balances::free_balance(3), 100);
assert_eq!(Balances::free_balance(4), 100);
assert_eq!(Balances::free_balance(5), 100);
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5)));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 20));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 30));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 25));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 10));
let candidate_3 = CandidateInfo { who: 3, deposit: 30 };
let candidate_4 = CandidateInfo { who: 4, deposit: 25 };
let candidate_5 = CandidateInfo { who: 5, deposit: 10 };
assert_eq!(
<crate::CandidateList<Test>>::get().iter().cloned().collect::<Vec<_>>(),
vec![candidate_5, candidate_4, candidate_3]
);
});
}
#[test]
fn leave_intent() {
new_test_ext().execute_with(|| {
// register a candidate.
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_eq!(Balances::free_balance(3), 90);
// register too so can leave above min candidates
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5)));
assert_eq!(Balances::free_balance(5), 90);
// cannot leave if not candidate.
assert_noop!(
CollatorSelection::leave_intent(RuntimeOrigin::signed(4)),
Error::<Test>::NotCandidate
);
// bond is returned
assert_ok!(CollatorSelection::leave_intent(RuntimeOrigin::signed(3)));
assert_eq!(Balances::free_balance(3), 100);
assert_eq!(CollatorSelection::last_authored_block(3), 0);
});
}
#[test]
fn authorship_event_handler() {
new_test_ext().execute_with(|| {
// put 100 in the pot + 5 for ED
Balances::make_free_balance_be(&CollatorSelection::account_id(), 105);
// 4 is the default author.
assert_eq!(Balances::free_balance(4), 100);
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
// triggers `note_author`
Authorship::on_initialize(1);
// tuple of (id, deposit).
let collator = CandidateInfo { who: 4, deposit: 10 };
assert_eq!(
<crate::CandidateList<Test>>::get().iter().cloned().collect::<Vec<_>>(),
vec![collator]
);
assert_eq!(CollatorSelection::last_authored_block(4), 0);
// half of the pot goes to the collator who's the author (4 in tests).
assert_eq!(Balances::free_balance(4), 140);
// half + ED stays.
assert_eq!(Balances::free_balance(CollatorSelection::account_id()), 55);
});
}
#[test]
fn fees_edgecases() {
new_test_ext().execute_with(|| {
// Nothing panics, no reward when no ED in balance
Authorship::on_initialize(1);
// put some money into the pot at ED
Balances::make_free_balance_be(&CollatorSelection::account_id(), 5);
// 4 is the default author.
assert_eq!(Balances::free_balance(4), 100);
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
// triggers `note_author`
Authorship::on_initialize(1);
// tuple of (id, deposit).
let collator = CandidateInfo { who: 4, deposit: 10 };
assert_eq!(
<crate::CandidateList<Test>>::get().iter().cloned().collect::<Vec<_>>(),
vec![collator]
);
assert_eq!(CollatorSelection::last_authored_block(4), 0);
// Nothing received
assert_eq!(Balances::free_balance(4), 90);
// all fee stays
assert_eq!(Balances::free_balance(CollatorSelection::account_id()), 5);
});
}
#[test]
fn session_management_single_candidate() {
new_test_ext().execute_with(|| {
initialize_to_block(1);
assert_eq!(SessionChangeBlock::get(), 0);
assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
initialize_to_block(4);
assert_eq!(SessionChangeBlock::get(), 0);
assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
// add a new collator
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
// session won't see this.
assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
// but we have a new candidate.
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 1);
initialize_to_block(10);
assert_eq!(SessionChangeBlock::get(), 10);
// pallet-session has 1 session delay; current validators are the same.
assert_eq!(Session::validators(), vec![1, 2]);
// queued ones are changed, and now we have 3.
assert_eq!(Session::queued_keys().len(), 3);
// session handlers (aura, et. al.) cannot see this yet.
assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
initialize_to_block(20);
assert_eq!(SessionChangeBlock::get(), 20);
// changed are now reflected to session handlers.
assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3]);
});
}
#[test]
fn session_management_max_candidates() {
new_test_ext().execute_with(|| {
initialize_to_block(1);
assert_eq!(SessionChangeBlock::get(), 0);
assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
initialize_to_block(4);
assert_eq!(SessionChangeBlock::get(), 0);
assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5)));
// session won't see this.
assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
// but we have a new candidate.
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 3);
initialize_to_block(10);
assert_eq!(SessionChangeBlock::get(), 10);
// pallet-session has 1 session delay; current validators are the same.
assert_eq!(Session::validators(), vec![1, 2]);
// queued ones are changed, and now we have 4.
assert_eq!(Session::queued_keys().len(), 4);
// session handlers (aura, et. al.) cannot see this yet.
assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
initialize_to_block(20);
assert_eq!(SessionChangeBlock::get(), 20);
// changed are now reflected to session handlers.
assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3, 4]);
});
}
#[test]
fn session_management_increase_bid_with_list_update() {
new_test_ext().execute_with(|| {
initialize_to_block(1);
assert_eq!(SessionChangeBlock::get(), 0);
assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
initialize_to_block(4);
assert_eq!(SessionChangeBlock::get(), 0);
assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5)));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 60));
// session won't see this.
assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
// but we have a new candidate.
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 3);
initialize_to_block(10);
assert_eq!(SessionChangeBlock::get(), 10);
// pallet-session has 1 session delay; current validators are the same.
assert_eq!(Session::validators(), vec![1, 2]);
// queued ones are changed, and now we have 4.
assert_eq!(Session::queued_keys().len(), 4);
// session handlers (aura, et. al.) cannot see this yet.
assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
initialize_to_block(20);
assert_eq!(SessionChangeBlock::get(), 20);
// changed are now reflected to session handlers.
assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 5, 3]);
});
}
#[test]
fn session_management_candidate_list_eager_sort() {
new_test_ext().execute_with(|| {
initialize_to_block(1);
assert_eq!(SessionChangeBlock::get(), 0);
assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
initialize_to_block(4);
assert_eq!(SessionChangeBlock::get(), 0);
assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5)));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 60));
// session won't see this.
assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
// but we have a new candidate.
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 3);
initialize_to_block(10);
assert_eq!(SessionChangeBlock::get(), 10);
// pallet-session has 1 session delay; current validators are the same.
assert_eq!(Session::validators(), vec![1, 2]);
// queued ones are changed, and now we have 4.
assert_eq!(Session::queued_keys().len(), 4);
// session handlers (aura, et. al.) cannot see this yet.
assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
initialize_to_block(20);
assert_eq!(SessionChangeBlock::get(), 20);
// changed are now reflected to session handlers.
assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 5, 3]);
});
}
#[test]
fn session_management_reciprocal_outbidding() {
new_test_ext().execute_with(|| {
initialize_to_block(1);
assert_eq!(SessionChangeBlock::get(), 0);
assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
initialize_to_block(4);
assert_eq!(SessionChangeBlock::get(), 0);
assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5)));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 60));
initialize_to_block(5);
// candidates 3 and 4 saw they were outbid and preemptively bid more
// than 5 in the next block.
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 70));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 70));
// session won't see this.
assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
// but we have a new candidate.
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 3);
initialize_to_block(10);
assert_eq!(SessionChangeBlock::get(), 10);
// pallet-session has 1 session delay; current validators are the same.
assert_eq!(Session::validators(), vec![1, 2]);
// queued ones are changed, and now we have 4.
assert_eq!(Session::queued_keys().len(), 4);
// session handlers (aura, et. al.) cannot see this yet.
assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
initialize_to_block(20);
assert_eq!(SessionChangeBlock::get(), 20);
// changed are now reflected to session handlers.
assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 4, 3]);
});
}
#[test]
fn session_management_decrease_bid_after_auction() {
new_test_ext().execute_with(|| {
initialize_to_block(1);
assert_eq!(SessionChangeBlock::get(), 0);
assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
initialize_to_block(4);
assert_eq!(SessionChangeBlock::get(), 0);
assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5)));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 60));
initialize_to_block(5);
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(4), 70));
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(3), 70));
initialize_to_block(5);
// candidate 5 saw it was outbid and wants to take back its bid, but
// not entirely so they still keep their place in the candidate list
// in case there is an opportunity in the future.
assert_ok!(CollatorSelection::update_bond(RuntimeOrigin::signed(5), 10));
// session won't see this.
assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
// but we have a new candidate.
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 3);
initialize_to_block(10);
assert_eq!(SessionChangeBlock::get(), 10);
// pallet-session has 1 session delay; current validators are the same.
assert_eq!(Session::validators(), vec![1, 2]);
// queued ones are changed, and now we have 4.
assert_eq!(Session::queued_keys().len(), 4);
// session handlers (aura, et. al.) cannot see this yet.
assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
initialize_to_block(20);
assert_eq!(SessionChangeBlock::get(), 20);
// changed are now reflected to session handlers.
assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 4, 3]);
});
}
#[test]
fn kick_mechanism() {
new_test_ext().execute_with(|| {
// add a new collator
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
initialize_to_block(10);
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 2);
initialize_to_block(20);
assert_eq!(SessionChangeBlock::get(), 20);
// 4 authored this block, gets to stay 3 was kicked
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 1);
// 3 will be kicked after 1 session delay
assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3, 4]);
// tuple of (id, deposit).
let collator = CandidateInfo { who: 4, deposit: 10 };
assert_eq!(
<crate::CandidateList<Test>>::get().iter().cloned().collect::<Vec<_>>(),
vec![collator]
);
assert_eq!(CollatorSelection::last_authored_block(4), 20);
initialize_to_block(30);
// 3 gets kicked after 1 session delay
assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 4]);
// kicked collator gets funds back
assert_eq!(Balances::free_balance(3), 100);
});
}
#[test]
fn should_not_kick_mechanism_too_few() {
new_test_ext().execute_with(|| {
// remove the invulnerables and add new collators 3 and 5
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 0);
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
assert_ok!(CollatorSelection::remove_invulnerable(
RuntimeOrigin::signed(RootAccount::get()),
1
));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5)));
assert_ok!(CollatorSelection::remove_invulnerable(
RuntimeOrigin::signed(RootAccount::get()),
2
));
initialize_to_block(10);
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 2);
initialize_to_block(20);
assert_eq!(SessionChangeBlock::get(), 20);
// 4 authored this block, 3 is kicked, 5 stays because of too few collators
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 1);
// 3 will be kicked after 1 session delay
assert_eq!(SessionHandlerCollators::get(), vec![3, 5]);
// tuple of (id, deposit).
let collator = CandidateInfo { who: 3, deposit: 10 };
assert_eq!(
<crate::CandidateList<Test>>::get().iter().cloned().collect::<Vec<_>>(),
vec![collator]
);
assert_eq!(CollatorSelection::last_authored_block(4), 20);
initialize_to_block(30);
// 3 gets kicked after 1 session delay
assert_eq!(SessionHandlerCollators::get(), vec![3]);
// kicked collator gets funds back
assert_eq!(Balances::free_balance(5), 100);
});
}
#[test]
fn should_kick_invulnerables_from_candidates_on_session_change() {
new_test_ext().execute_with(|| {
assert_eq!(<crate::CandidateList<Test>>::get().iter().count(), 0);
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)));
assert_ok!(CollatorSelection::register_as_candidate(RuntimeOrigin::signed(4)));
assert_eq!(Balances::free_balance(3), 90);
assert_eq!(Balances::free_balance(4), 90);
assert_ok!(CollatorSelection::set_invulnerables(
RuntimeOrigin::signed(RootAccount::get()),
vec![1, 2, 3]
));
// tuple of (id, deposit).
let collator_3 = CandidateInfo { who: 3, deposit: 10 };
let collator_4 = CandidateInfo { who: 4, deposit: 10 };
let actual_candidates =
<crate::CandidateList<Test>>::get().iter().cloned().collect::<Vec<_>>();
assert_eq!(actual_candidates, vec![collator_4.clone(), collator_3]);
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2, 3]);
// session change
initialize_to_block(10);
// 3 is removed from candidates
assert_eq!(
<crate::CandidateList<Test>>::get().iter().cloned().collect::<Vec<_>>(),
vec![collator_4]
);
// but not from invulnerables
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2, 3]);
// and it got its deposit back
assert_eq!(Balances::free_balance(3), 100);
});
}
#[test]
#[should_panic = "duplicate invulnerables in genesis."]
fn cannot_set_genesis_value_twice() {
sp_tracing::try_init_simple();
let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
let invulnerables = vec![1, 1];
let collator_selection = collator_selection::GenesisConfig::<Test> {
desired_candidates: 2,
candidacy_bond: 10,
invulnerables,
};
// collator selection must be initialized before session.
collator_selection.assimilate_storage(&mut t).unwrap();
}