difftreelog
refactor! decouple pallet-collator-selection from pallet-configuration (#962)
in: master
* feat!: decoupling configuration&collators pallets BREAKING CHANGE: pallet `collator-selection` no longer has a tight coupling with the `configuration` pallet * refactor(collator-selection): type bounds, tests * refactor(collator-selection): tests * add ci step for banchmark tests * add opsFee tests ---------
15 files changed
.github/workflows/yarn-dev.ymldiffbeforeafterboth--- a/.github/workflows/yarn-dev.yml
+++ b/.github/workflows/yarn-dev.yml
@@ -87,6 +87,18 @@
run: |
echo "url is ${{ steps.test-report.outputs.runHtmlUrl }}"
+ - name: Run benchmark mintFee tests
+ working-directory: tests
+ run: |
+ yarn install
+ npx ts-node --esm ./src/benchmarks/mintFee/index.ts
+
+ - name: Run benchmark opsFee tests
+ working-directory: tests
+ run: |
+ yarn install
+ npx ts-node --esm ./src/benchmarks/opsFee/index.ts
+
- name: Stop running containers
if: always() # run this step always
run: docker-compose -f ".docker/docker-compose.${{ matrix.network }}.yml" down
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -6371,7 +6371,7 @@
[[package]]
name = "pallet-collator-selection"
-version = "4.0.0"
+version = "5.0.0"
dependencies = [
"frame-benchmarking",
"frame-support",
@@ -6380,7 +6380,6 @@
"pallet-aura",
"pallet-authorship",
"pallet-balances",
- "pallet-configuration",
"pallet-session",
"pallet-timestamp",
"parity-scale-codec",
@@ -6435,7 +6434,7 @@
[[package]]
name = "pallet-configuration"
-version = "0.1.3"
+version = "0.2.0"
dependencies = [
"fp-evm",
"frame-benchmarking",
pallets/collator-selection/CHANGELOG.mddiffbeforeafterboth--- a/pallets/collator-selection/CHANGELOG.md
+++ b/pallets/collator-selection/CHANGELOG.md
@@ -4,22 +4,28 @@
<!-- bureaucracy goes here -->
+## [5.0.0] - 2023-06-27
+
+### Major change
+
+- The dependency (tight coupling) this pallet on the configuration pallet has been removed.
+
## [4.0.0] - 2022-12-29
### Added
- Entire functionality moved over from cumulus/pallet-collator-selection (v3.0.0). Refactored business logic:
- - Added an extra step for candidacy, `get_license`, at which payment happens. The number of license holders is unlimited.
- - Only licensed accounts can apply for candidacy with `onboard`. No extra deposits are made.
- - Active candidates may `offboard`, but they will retain their license.
- - Deposit is returned, and candidacy possibly removed with `release_license`.
- - License can be forcibly forfeited and candidacy removed with `force_release_license`.
- - Failing collators' deposits will now be fully slashed, and funds will be redirected to Treasury.
- - Unify `MaxInvulnerables` and `MaxCandidates` into `MaxCollators`.
- - Remove `MinCandidates`.
- - Minimal amount of invulnerables is now 1.
- - Both invulnerables and candidates count against the limits together, however invulnerables ignore `DesiredCollators`.
- - `KickThreshold` is made configurable.
- - `DesiredCollators`, `LicenseBond`, and `KickThreshold` are moved to and referenced from `pallet-configuration`.
- - Naming changes to better reflect the new functionality.
- - More minor changes, tests, benchmarks, etc.
\ No newline at end of file
+ - Added an extra step for candidacy, `get_license`, at which payment happens. The number of license holders is unlimited.
+ - Only licensed accounts can apply for candidacy with `onboard`. No extra deposits are made.
+ - Active candidates may `offboard`, but they will retain their license.
+ - Deposit is returned, and candidacy possibly removed with `release_license`.
+ - License can be forcibly forfeited and candidacy removed with `force_release_license`.
+ - Failing collators' deposits will now be fully slashed, and funds will be redirected to Treasury.
+ - Unify `MaxInvulnerables` and `MaxCandidates` into `MaxCollators`.
+ - Remove `MinCandidates`.
+ - Minimal amount of invulnerables is now 1.
+ - Both invulnerables and candidates count against the limits together, however invulnerables ignore `DesiredCollators`.
+ - `KickThreshold` is made configurable.
+ - `DesiredCollators`, `LicenseBond`, and `KickThreshold` are moved to and referenced from `pallet-configuration`.
+ - Naming changes to better reflect the new functionality.
+ - More minor changes, tests, benchmarks, etc.
pallets/collator-selection/Cargo.tomldiffbeforeafterboth--- a/pallets/collator-selection/Cargo.toml
+++ b/pallets/collator-selection/Cargo.toml
@@ -6,7 +6,7 @@
license = "GPLv3"
name = "pallet-collator-selection"
repository = "https://github.com/UniqueNetwork/unique-chain"
-version = "4.0.0"
+version = "5.0.0"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
@@ -23,7 +23,6 @@
frame-support = { workspace = true }
frame-system = { workspace = true }
pallet-authorship = { workspace = true }
-pallet-configuration = { workspace = true }
pallet-session = { workspace = true }
sp-runtime = { workspace = true }
sp-staking = { workspace = true }
@@ -55,8 +54,6 @@
"frame-system/std",
"log/std",
"pallet-authorship/std",
- 'pallet-aura/std',
- 'pallet-balances/std',
"pallet-session/std",
"rand/std",
"scale-info/std",
@@ -64,6 +61,8 @@
"sp-runtime/std",
"sp-staking/std",
"sp-std/std",
+ 'pallet-aura/std',
+ 'pallet-balances/std',
]
try-runtime = ["frame-support/try-runtime"]
pallets/collator-selection/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/collator-selection/src/benchmarking.rs
+++ b/pallets/collator-selection/src/benchmarking.rs
@@ -35,7 +35,7 @@
use super::*;
#[allow(unused)]
-use crate::Pallet as CollatorSelection;
+use crate::{Pallet as CollatorSelection, BalanceOf};
use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, whitelisted_caller};
use frame_support::{
assert_ok,
@@ -49,11 +49,6 @@
use frame_system::{EventRecord, RawOrigin};
use pallet_authorship::EventHandler;
use pallet_session::{self as session, SessionManager};
-use pallet_configuration::{
- self as configuration, BalanceOf,
- CollatorSelectionDesiredCollatorsOverride as DesiredCollators,
- CollatorSelectionLicenseBondOverride as LicenseBond,
-};
use sp_std::prelude::*;
const SEED: u32 = 0;
@@ -117,7 +112,7 @@
validators.into_iter().map(|(who, _)| who).collect()
}
-fn register_invulnerables<T: Config + configuration::Config>(count: u32) {
+fn register_invulnerables<T: Config>(count: u32) {
let candidates = (0..count)
.map(|c| account("candidate", c, SEED))
.collect::<Vec<_>>();
@@ -131,33 +126,27 @@
}
}
-fn register_candidates<T: Config + configuration::Config>(count: u32) {
+fn register_candidates<T: Config>(count: u32) {
let candidates = (0..count)
.map(|c| account("candidate", c, SEED))
.collect::<Vec<_>>();
- assert!(
- <LicenseBond<T>>::get() > 0u32.into(),
- "Bond cannot be zero!"
- );
+ assert!(T::LicenseBond::get() > 0u32.into(), "Bond cannot be zero!");
for who in candidates {
- T::Currency::set_balance(&who, <LicenseBond<T>>::get() * 2u32.into());
+ T::Currency::set_balance(&who, T::LicenseBond::get() * 2u32.into());
<CollatorSelection<T>>::get_license(RawOrigin::Signed(who.clone()).into()).unwrap();
<CollatorSelection<T>>::onboard(RawOrigin::Signed(who).into()).unwrap();
}
}
-fn get_licenses<T: Config + configuration::Config>(count: u32) {
+fn get_licenses<T: Config>(count: u32) {
let candidates = (0..count)
.map(|c| account("candidate", c, SEED))
.collect::<Vec<_>>();
- assert!(
- <LicenseBond<T>>::get() > 0u32.into(),
- "Bond cannot be zero!"
- );
+ assert!(T::LicenseBond::get() > 0u32.into(), "Bond cannot be zero!");
for who in candidates {
- T::Currency::set_balance(&who, <LicenseBond<T>>::get() * 2u32.into());
+ T::Currency::set_balance(&who, T::LicenseBond::get() * 2u32.into());
<CollatorSelection<T>>::get_license(RawOrigin::Signed(who.clone()).into()).unwrap();
}
}
@@ -165,7 +154,7 @@
/// `Currency::minimum_balance` was used originally, but in unique-chain, we have
/// zero existential deposit, thus triggering zero bond assertion.
fn balance_unit<T: Config>() -> BalanceOf<T> {
- 200u32.into()
+ T::LicenseBond::get()
}
/// Our benchmarking environment already has invulnerables registered.
@@ -173,7 +162,7 @@
benchmarks! {
where_clause { where
- T: pallet_authorship::Config + session::Config + configuration::Config
+ T: Config + pallet_authorship::Config + session::Config
}
// todo:collator this and all the following do not work for some reason, going all the way up to 10 in length
@@ -188,7 +177,7 @@
let new_invulnerable: T::AccountId = whitelisted_caller();
let bond: BalanceOf<T> = balance_unit::<T>() * 2u32.into();
- T::Currency::set_balance(&new_invulnerable, bond.clone());
+ <T as Config>::Currency::set_balance(&new_invulnerable, bond);
<session::Pallet<T>>::set_keys(
RawOrigin::Signed(new_invulnerable.clone()).into(),
@@ -225,15 +214,13 @@
get_license {
let c in 1 .. T::MaxCollators::get() - 1;
-
- <LicenseBond<T>>::put(balance_unit::<T>());
register_validators::<T>(c);
get_licenses::<T>(c);
let caller: T::AccountId = whitelisted_caller();
let bond: BalanceOf<T> = balance_unit::<T>() * 2u32.into();
- T::Currency::set_balance(&caller, bond.clone());
+ T::Currency::set_balance(&caller, bond);
<session::Pallet<T>>::set_keys(
RawOrigin::Signed(caller.clone()).into(),
@@ -251,15 +238,12 @@
onboard {
let c in 1 .. T::MaxCollators::get() - INITIAL_INVULNERABLES - 1;
- <LicenseBond<T>>::put(balance_unit::<T>());
- <DesiredCollators<T>>::put(c + INITIAL_INVULNERABLES + 1);
-
register_validators::<T>(c);
register_candidates::<T>(c);
let caller: T::AccountId = whitelisted_caller();
let bond: BalanceOf<T> = balance_unit::<T>() * 2u32.into();
- T::Currency::set_balance(&caller, bond.clone());
+ T::Currency::set_balance(&caller, bond);
let origin = RawOrigin::Signed(caller.clone());
@@ -279,9 +263,7 @@
// worst case is the last candidate leaving.
offboard {
- let c in 1 .. T::MaxCollators::get();
- <LicenseBond<T>>::put(balance_unit::<T>());
- <DesiredCollators<T>>::put(c + INITIAL_INVULNERABLES);
+ let c in 1 .. T::MaxCollators::get() - INITIAL_INVULNERABLES;
register_validators::<T>(c);
register_candidates::<T>(c);
@@ -295,10 +277,8 @@
// worst case is the last candidate leaving.
release_license {
- let c in 1 .. T::MaxCollators::get();
+ let c in 1 .. T::MaxCollators::get() - INITIAL_INVULNERABLES;
let bond = balance_unit::<T>();
- <LicenseBond<T>>::put(bond);
- <DesiredCollators<T>>::put(c + INITIAL_INVULNERABLES);
register_validators::<T>(c);
register_candidates::<T>(c);
@@ -312,10 +292,8 @@
// worst case is the last candidate leaving.
force_release_license {
- let c in 1 .. T::MaxCollators::get();
+ let c in 1 .. T::MaxCollators::get() - INITIAL_INVULNERABLES;
let bond = balance_unit::<T>();
- <LicenseBond<T>>::put(bond);
- <DesiredCollators<T>>::put(c + INITIAL_INVULNERABLES);
register_validators::<T>(c);
register_candidates::<T>(c);
@@ -334,7 +312,6 @@
// worst case is paying a non-existing candidate account.
note_author {
- <LicenseBond<T>>::put(balance_unit::<T>());
T::Currency::set_balance(
&<CollatorSelection<T>>::account_id(),
balance_unit::<T>() * 4u32.into(),
@@ -353,11 +330,9 @@
// worst case for new session.
new_session {
- let r in 1 .. T::MaxCollators::get();
- let c in 1 .. T::MaxCollators::get();
+ let r in 1 .. T::MaxCollators::get() - INITIAL_INVULNERABLES;
+ let c in 1 .. T::MaxCollators::get() - INITIAL_INVULNERABLES;
- <LicenseBond<T>>::put(balance_unit::<T>());
- <DesiredCollators<T>>::put(c + INITIAL_INVULNERABLES);
frame_system::Pallet::<T>::set_block_number(0u32.into());
register_validators::<T>(c);
pallets/collator-selection/src/lib.rsdiffbeforeafterboth1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617// Original license:18// Copyright (C) 2021 Parity Technologies (UK) Ltd.19// SPDX-License-Identifier: Apache-2.02021// Licensed under the Apache License, Version 2.0 (the "License");22// you may not use this file except in compliance with the License.23// You may obtain a copy of the License at24//25// http://www.apache.org/licenses/LICENSE-2.026//27// Unless required by applicable law or agreed to in writing, software28// distributed under the License is distributed on an "AS IS" BASIS,29// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.30// See the License for the specific language governing permissions and31// limitations under the License.3233// todo:collator documentation34//! Collator Selection pallet.35//!36//! A pallet to manage collators in a parachain.37//!38//! ## Overview39//!40//! The Collator Selection pallet manages the collators of a parachain. **Collation is _not_ a41//! secure activity** and this pallet does not implement any game-theoretic mechanisms to meet BFT42//! safety assumptions of the chosen set.43//!44//! ## Terminology45//!46//! - Collator: A parachain block producer.47//! - Bond: An amount of `Balance` _reserved_ for candidate registration.48//! - Invulnerable: An account guaranteed to be in the collator set.49//!50//! ## Implementation51//!52//! The final `Collators` are aggregated from two individual lists:53//!54//! 1. [`Invulnerables`]: a set of collators appointed by governance. These accounts will always be55//! collators.56//! 2. [`Candidates`]: these are *candidates to the collation task* and may or may not be elected as57//! a final collator.58//!59//! The current implementation resolves congestion of [`Candidates`] in a first-come-first-serve60//! manner.61//!62//! Candidates will not be allowed to get kicked or leave_intent if the total number of candidates63//! fall below MinCandidates. This is for potential disaster recovery scenarios.64//!65//! ### Rewards66//!67//! The Collator Selection pallet maintains an on-chain account (the "Pot"). In each block, the68//! collator who authored it receives:69//!70//! - Half the value of the Pot.71//! - Half the value of the transaction fees within the block. The other half of the transaction72//! fees are deposited into the Pot.73//!74//! To initiate rewards an ED needs to be transferred to the pot address.75//!76//! Note: Eventually the Pot distribution may be modified as discussed in77//! [this issue](https://github.com/paritytech/statemint/issues/21#issuecomment-810481073).7879#![cfg_attr(not(feature = "std"), no_std)]8081pub use pallet::*;8283#[cfg(test)]84mod mock;8586#[cfg(test)]87mod tests;8889#[cfg(feature = "runtime-benchmarks")]90mod benchmarking;91pub mod weights;9293#[frame_support::pallet]94pub mod pallet {95 use super::*;96 pub use crate::weights::WeightInfo;97 use core::ops::Div;98 use frame_support::{99 dispatch::{DispatchClass, DispatchResultWithPostInfo},100 inherent::Vec,101 pallet_prelude::*,102 sp_runtime::traits::{AccountIdConversion, CheckedSub, Saturating, Zero},103 traits::{104 EnsureOrigin,105 fungible::{Balanced, BalancedHold, Inspect, InspectHold, Mutate, MutateHold},106 ValidatorRegistration,107 tokens::{Precision, Preservation},108 },109 BoundedVec, PalletId,110 };111 use frame_system::pallet_prelude::*;112 use pallet_session::SessionManager;113 use sp_runtime::{Perbill, traits::Convert};114 use pallet_configuration::{115 CollatorSelectionDesiredCollatorsOverride as DesiredCollators,116 CollatorSelectionLicenseBondOverride as LicenseBond,117 CollatorSelectionKickThresholdOverride as KickThreshold, BalanceOf,118 };119 use sp_staking::SessionIndex;120121 /// A convertor from collators id. Since this pallet does not have stash/controller, this is122 /// just identity.123 pub struct IdentityCollator;124 impl<T> sp_runtime::traits::Convert<T, Option<T>> for IdentityCollator {125 fn convert(t: T) -> Option<T> {126 Some(t)127 }128 }129130 /// Configure the pallet by specifying the parameters and types on which it depends.131 #[pallet::config]132 pub trait Config: frame_system::Config + pallet_configuration::Config {133 /// Overarching event type.134 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;135136 /// Origin that can dictate updating parameters of this pallet.137 type UpdateOrigin: EnsureOrigin<Self::RuntimeOrigin>;138139 /// Account Identifier that holds the chain's treasury.140 type TreasuryAccountId: Get<Self::AccountId>;141142 /// Account Identifier from which the internal Pot is generated.143 type PotId: Get<PalletId>;144145 /// Maximum number of candidates and invulnerables that we should have. This is enforced in code.146 type MaxCollators: Get<u32>;147148 /// If kicked, how much of the collator's deposit will be slashed and sent to the slash destination.149 type SlashRatio: Get<Perbill>;150151 /// A stable ID for a validator.152 type ValidatorId: Member + Parameter;153154 /// A conversion from account ID to validator ID.155 ///156 /// Its cost must be at most one storage read.157 type ValidatorIdOf: Convert<Self::AccountId, Option<Self::ValidatorId>>;158159 /// Validate a user is registered160 type ValidatorRegistration: ValidatorRegistration<Self::ValidatorId>;161162 /// The weight information of this pallet.163 type WeightInfo: WeightInfo;164165 #[pallet::constant]166 type LicenceBondIdentifier: Get<<<Self as pallet_configuration::Config>::Currency as InspectHold<Self::AccountId>>::Reason>;167 }168169 #[pallet::pallet]170 pub struct Pallet<T>(_);171172 /// The invulnerable, fixed collators.173 #[pallet::storage]174 #[pallet::getter(fn invulnerables)]175 pub type Invulnerables<T: Config> =176 StorageValue<_, BoundedVec<T::AccountId, T::MaxCollators>, ValueQuery>;177178 /// The (community) collation license holders.179 #[pallet::storage]180 #[pallet::getter(fn license_deposit_of)]181 pub type LicenseDepositOf<T: Config> =182 StorageMap<_, Blake2_128Concat, T::AccountId, BalanceOf<T>, ValueQuery>;183184 /// The (community, limited) collation candidates.185 #[pallet::storage]186 #[pallet::getter(fn candidates)]187 pub type Candidates<T: Config> =188 StorageValue<_, BoundedVec<T::AccountId, T::MaxCollators>, ValueQuery>;189190 /// Last block authored by collator.191 #[pallet::storage]192 #[pallet::getter(fn last_authored_block)]193 pub type LastAuthoredBlock<T: Config> =194 StorageMap<_, Twox64Concat, T::AccountId, T::BlockNumber, ValueQuery>;195196 #[pallet::genesis_config]197 pub struct GenesisConfig<T: Config> {198 pub invulnerables: Vec<T::AccountId>,199 }200201 #[cfg(feature = "std")]202 impl<T: Config> Default for GenesisConfig<T> {203 fn default() -> Self {204 Self {205 invulnerables: Default::default(),206 }207 }208 }209210 #[pallet::genesis_build]211 impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {212 fn build(&self) {213 let duplicate_invulnerables = self214 .invulnerables215 .iter()216 .collect::<std::collections::BTreeSet<_>>();217 assert!(218 duplicate_invulnerables.len() == self.invulnerables.len(),219 "duplicate invulnerables in genesis."220 );221222 let bounded_invulnerables =223 BoundedVec::<_, T::MaxCollators>::try_from(self.invulnerables.clone())224 .expect("genesis invulnerables are more than T::MaxCollators");225226 <Invulnerables<T>>::put(bounded_invulnerables);227 }228 }229230 #[pallet::event]231 #[pallet::generate_deposit(pub(super) fn deposit_event)]232 pub enum Event<T: Config> {233 InvulnerableAdded {234 invulnerable: T::AccountId,235 },236 InvulnerableRemoved {237 invulnerable: T::AccountId,238 },239 LicenseObtained {240 account_id: T::AccountId,241 deposit: BalanceOf<T>,242 },243 LicenseReleased {244 account_id: T::AccountId,245 deposit_returned: BalanceOf<T>,246 },247 CandidateAdded {248 account_id: T::AccountId,249 },250 CandidateRemoved {251 account_id: T::AccountId,252 },253 }254255 // Errors inform users that something went wrong.256 #[pallet::error]257 pub enum Error<T> {258 /// Too many candidates259 TooManyCandidates,260 /// Unknown error261 Unknown,262 /// Permission issue263 Permission,264 /// User already holds license to collate265 AlreadyHoldingLicense,266 /// User does not hold a license to collate267 NoLicense,268 /// User is already a candidate269 AlreadyCandidate,270 /// User is not a candidate271 NotCandidate,272 /// Too many invulnerables273 TooManyInvulnerables,274 /// Too few invulnerables275 TooFewInvulnerables,276 /// User is already an Invulnerable277 AlreadyInvulnerable,278 /// User is not an Invulnerable279 NotInvulnerable,280 /// Account has no associated validator ID281 NoAssociatedValidatorId,282 /// Validator ID is not yet registered283 ValidatorNotRegistered,284 }285286 #[pallet::hooks]287 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}288289 #[pallet::call]290 impl<T: Config> Pallet<T> {291 /// Add a collator to the list of invulnerable (fixed) collators.292 #[pallet::call_index(0)]293 #[pallet::weight(<T as Config>::WeightInfo::add_invulnerable(T::MaxCollators::get()))]294 pub fn add_invulnerable(295 origin: OriginFor<T>,296 new: T::AccountId,297 ) -> DispatchResultWithPostInfo {298 T::UpdateOrigin::ensure_origin(origin)?;299300 // check if the new invulnerable has associated validator keys before it is added301 let validator_key = T::ValidatorIdOf::convert(new.clone())302 .ok_or(Error::<T>::NoAssociatedValidatorId)?;303 ensure!(304 T::ValidatorRegistration::is_registered(&validator_key),305 Error::<T>::ValidatorNotRegistered306 );307 if Self::invulnerables().contains(&new) {308 return Ok(().into());309 }310311 <Invulnerables<T>>::try_append(new.clone())312 .map_err(|_| Error::<T>::TooManyInvulnerables)?;313314 // try to offboard the new invulnerable if it was a collator candidate before315 let _ = Self::try_remove_candidate(&new);316317 Self::deposit_event(Event::InvulnerableAdded { invulnerable: new });318 Ok(().into())319 }320321 /// Remove a collator from the list of invulnerable (fixed) collators.322 #[pallet::call_index(1)]323 #[pallet::weight(<T as Config>::WeightInfo::remove_invulnerable(T::MaxCollators::get()))]324 pub fn remove_invulnerable(325 origin: OriginFor<T>,326 who: T::AccountId,327 ) -> DispatchResultWithPostInfo {328 T::UpdateOrigin::ensure_origin(origin)?;329330 <Invulnerables<T>>::try_mutate(|invulnerables| -> DispatchResult {331 if invulnerables.len() <= 1 {332 return Err(Error::<T>::TooFewInvulnerables.into());333 }334335 let index = invulnerables336 .into_iter()337 .position(|r| *r == who)338 .ok_or(Error::<T>::NotInvulnerable)?;339 invulnerables.remove(index);340 Ok(())341 })?;342 Self::deposit_event(Event::InvulnerableRemoved { invulnerable: who });343 Ok(().into())344 }345346 /// Purchase a license on block collation for this account.347 /// It does not make it a collator candidate, use `onboard` afterward. The account must348 /// (a) already have registered session keys and (b) be able to reserve the `LicenseBond`.349 ///350 /// This call is not available to `Invulnerable` collators.351 #[pallet::call_index(2)]352 #[pallet::weight(<T as Config>::WeightInfo::get_license(T::MaxCollators::get()))]353 pub fn get_license(origin: OriginFor<T>) -> DispatchResultWithPostInfo {354 // register_as_candidate355 let who = ensure_signed(origin)?;356357 if LicenseDepositOf::<T>::contains_key(&who) {358 return Err(Error::<T>::AlreadyHoldingLicense.into());359 }360361 let validator_key = T::ValidatorIdOf::convert(who.clone())362 .ok_or(Error::<T>::NoAssociatedValidatorId)?;363 ensure!(364 T::ValidatorRegistration::is_registered(&validator_key),365 Error::<T>::ValidatorNotRegistered366 );367368 let deposit = <LicenseBond<T>>::get();369370 T::Currency::hold(&T::LicenceBondIdentifier::get(), &who, deposit)?;371 LicenseDepositOf::<T>::insert(who.clone(), deposit);372373 Self::deposit_event(Event::LicenseObtained {374 account_id: who,375 deposit,376 });377 Ok(().into()) // Some(T::WeightInfo::register_as_candidate(current_count as u32)).into())378 }379380 /// Register this account as a candidate for collators for next sessions.381 /// The account must already hold a license, and cannot offboard immediately during a session.382 ///383 /// This call is not available to `Invulnerable` collators.384 #[pallet::call_index(3)]385 #[pallet::weight(<T as Config>::WeightInfo::onboard(T::MaxCollators::get()))]386 pub fn onboard(origin: OriginFor<T>) -> DispatchResultWithPostInfo {387 // register_as_candidate388 let who = ensure_signed(origin)?;389390 // ensure the user obtained the license.391 ensure!(392 LicenseDepositOf::<T>::contains_key(&who),393 Error::<T>::NoLicense394 );395 // ensure we are below limit.396 let length = <Candidates<T>>::decode_len().unwrap_or_default()397 + <Invulnerables<T>>::decode_len().unwrap_or_default();398 ensure!(399 (length as u32) < <DesiredCollators<T>>::get(),400 Error::<T>::TooManyCandidates401 );402 ensure!(403 !Self::invulnerables().contains(&who),404 Error::<T>::AlreadyInvulnerable405 );406407 let current_count =408 <Candidates<T>>::try_mutate(|candidates| -> Result<usize, DispatchError> {409 if candidates.iter().any(|candidate| *candidate == who) {410 Err(Error::<T>::AlreadyCandidate)?411 } else {412 candidates413 .try_push(who.clone())414 .map_err(|_| Error::<T>::TooManyCandidates)?;415 // First authored block is current block plus kick threshold to handle session delay416 <LastAuthoredBlock<T>>::insert(417 who.clone(),418 frame_system::Pallet::<T>::block_number() + <KickThreshold<T>>::get(),419 );420 Ok(candidates.len())421 }422 })?;423424 Self::deposit_event(Event::CandidateAdded { account_id: who });425 Ok(Some(<T as Config>::WeightInfo::onboard(current_count as u32)).into())426 }427428 /// Deregister `origin` as a collator candidate. Note that the collator can only leave on429 /// session change. The license to `onboard` later at any other time will remain.430 #[pallet::call_index(4)]431 #[pallet::weight(<T as Config>::WeightInfo::offboard(T::MaxCollators::get()))]432 pub fn offboard(origin: OriginFor<T>) -> DispatchResultWithPostInfo {433 // leave_intent434 let who = ensure_signed(origin)?;435 let current_count = Self::try_remove_candidate(&who)?;436437 Ok(Some(<T as Config>::WeightInfo::offboard(current_count as u32)).into())438 }439440 /// Forfeit `origin`'s own license. The `LicenseBond` will be unreserved immediately.441 ///442 /// This call is not available to `Invulnerable` collators.443 #[pallet::call_index(5)]444 #[pallet::weight(<T as Config>::WeightInfo::release_license(T::MaxCollators::get()))]445 pub fn release_license(origin: OriginFor<T>) -> DispatchResultWithPostInfo {446 // leave_intent447 let who = ensure_signed(origin)?;448449 let current_count = Self::try_remove_candidate_and_release_license(&who, false, true)?;450451 Ok(Some(<T as Config>::WeightInfo::release_license(452 current_count as u32,453 ))454 .into())455 }456457 /// Force deregister `origin` as a collator candidate as a governing authority, and revoke its license.458 /// Note that the collator can only leave on session change.459 /// The `LicenseBond` will be unreserved and returned immediately.460 ///461 /// This call is, of course, not applicable to `Invulnerable` collators.462 #[pallet::call_index(6)]463 #[pallet::weight(<T as Config>::WeightInfo::force_release_license(T::MaxCollators::get()))]464 pub fn force_release_license(465 origin: OriginFor<T>,466 who: T::AccountId,467 ) -> DispatchResultWithPostInfo {468 // leave_intent469 T::UpdateOrigin::ensure_origin(origin)?;470471 let current_count = Self::try_remove_candidate_and_release_license(&who, false, true)?;472473 Ok(Some(<T as Config>::WeightInfo::force_release_license(474 current_count as u32,475 ))476 .into())477 }478 }479480 impl<T: Config> Pallet<T> {481 /// Get a unique, inaccessible account id from the `PotId`.482 pub fn account_id() -> T::AccountId {483 T::PotId::get().into_account_truncating()484 }485486 /// Removes a candidate and their license, optionally slashed and optionally ignoring,487 /// whether or not they actually are a candidate.488 fn try_remove_candidate_and_release_license(489 who: &T::AccountId,490 should_slash: bool,491 ignore_if_not_candidate: bool,492 ) -> Result<usize, DispatchError> {493 let current_count = Self::try_remove_candidate(who);494 let current_count = if ignore_if_not_candidate495 && current_count == Err(Error::<T>::NotCandidate.into())496 {497 <Candidates<T>>::decode_len().unwrap_or_default()498 } else {499 current_count?500 };501 Self::try_release_license(who, should_slash)?;502 Ok(current_count)503 }504505 /// Removes a candidate from the collator pool for the next session if they exist.506 fn try_remove_candidate(who: &T::AccountId) -> Result<usize, DispatchError> {507 let current_count =508 <Candidates<T>>::try_mutate(|candidates| -> Result<usize, DispatchError> {509 let index = candidates510 .iter()511 .position(|candidate| *candidate == *who)512 .ok_or(Error::<T>::NotCandidate)?;513 candidates.remove(index);514 <LastAuthoredBlock<T>>::remove(who.clone());515 Ok(candidates.len())516 })?;517 Self::deposit_event(Event::CandidateRemoved {518 account_id: who.clone(),519 });520 Ok(current_count)521 }522523 /// Removes a candidate if they exist and sends them back their deposit, optionally slashed.524 fn try_release_license(who: &T::AccountId, should_slash: bool) -> DispatchResult {525 let mut deposit_returned = BalanceOf::<T>::default();526 LicenseDepositOf::<T>::try_mutate_exists(who, |deposit| -> DispatchResult {527 if let Some(deposit) = deposit.take() {528 if should_slash {529 let slashed = T::SlashRatio::get() * deposit;530 let remaining = deposit - slashed;531532 let (imbalance, _) =533 T::Currency::slash(&T::LicenceBondIdentifier::get(), who, slashed);534 deposit_returned = remaining;535536 T::Currency::resolve(&T::TreasuryAccountId::get(), imbalance)537 .map_err(|_| DispatchError::Other("Failed to deposit imbalance"))?;538 } else {539 deposit_returned = deposit;540 }541542 T::Currency::release(543 &T::LicenceBondIdentifier::get(),544 who,545 deposit_returned,546 Precision::Exact,547 )?;548 Ok(())549 } else {550 Err(Error::<T>::NoLicense.into())551 }552 })?;553 Self::deposit_event(Event::LicenseReleased {554 account_id: who.clone(),555 deposit_returned,556 });557 Ok(())558 }559560 /// Assemble the current set of candidates and invulnerables into the next collator set.561 ///562 /// This is done on the fly, as frequent as we are told to do so, as the session manager.563 pub fn assemble_collators(564 candidates: BoundedVec<T::AccountId, T::MaxCollators>,565 ) -> Vec<T::AccountId> {566 let mut collators = Self::invulnerables().to_vec();567 collators.extend(candidates);568 collators569 }570571 /// Kicks out candidates that did not produce a block in the kick threshold572 /// and **confiscates** their deposits to the treasury.573 pub fn kick_stale_candidates(574 candidates: BoundedVec<T::AccountId, T::MaxCollators>,575 ) -> BoundedVec<T::AccountId, T::MaxCollators> {576 let now = frame_system::Pallet::<T>::block_number();577 let kick_threshold = <KickThreshold<T>>::get();578 candidates579 .into_iter()580 .filter_map(|c| {581 let last_block = <LastAuthoredBlock<T>>::get(c.clone());582 let since_last = now.saturating_sub(last_block);583 if since_last < kick_threshold {584 Some(c)585 } else {586 let outcome = Self::try_remove_candidate_and_release_license(&c, true, false);587 if let Err(why) = outcome {588 log::warn!("Failed to kick collator and release license {:?}", why);589 debug_assert!(false, "failed to kick collator and release license {why:?}");590 }591 None592 }593 })594 .collect::<Vec<_>>()595 .try_into()596 .expect("filter_map operation can't result in a bounded vec larger than its original; qed")597 }598 }599600 /// Keep track of number of authored blocks per authority, uncles are counted as well since601 /// they're a valid proof of being online.602 impl<T: Config + pallet_authorship::Config>603 pallet_authorship::EventHandler<T::AccountId, T::BlockNumber> for Pallet<T>604 {605 fn note_author(author: T::AccountId) {606 let pot = Self::account_id();607 // assumes an ED will be sent to pot.608 let reward = T::Currency::balance(&pot)609 .checked_sub(&T::Currency::minimum_balance())610 .unwrap_or_else(Zero::zero)611 .div(2u32.into());612613 if !reward.is_zero() {614 // `reward` is half of pot account minus ED, this should never fail.615 let _success = T::Currency::transfer(&pot, &author, reward, Preservation::Preserve);616 debug_assert!(_success.is_ok());617 }618 <LastAuthoredBlock<T>>::insert(author, frame_system::Pallet::<T>::block_number());619620 frame_system::Pallet::<T>::register_extra_weight_unchecked(621 <T as Config>::WeightInfo::note_author(),622 DispatchClass::Mandatory,623 );624 }625 }626627 /// Play the role of the session manager.628 impl<T: Config> SessionManager<T::AccountId> for Pallet<T> {629 fn new_session(index: SessionIndex) -> Option<Vec<T::AccountId>> {630 log::info!(631 "assembling new collators for new session {} at #{:?}",632 index,633 <frame_system::Pallet<T>>::block_number(),634 );635636 let candidates = Self::candidates();637 let candidates_len_before = candidates.len();638 let active_candidates = Self::kick_stale_candidates(candidates);639 let removed = candidates_len_before - active_candidates.len();640 let result = Self::assemble_collators(active_candidates);641642 frame_system::Pallet::<T>::register_extra_weight_unchecked(643 <T as Config>::WeightInfo::new_session(644 candidates_len_before as u32,645 removed as u32,646 ),647 DispatchClass::Mandatory,648 );649 Some(result)650 }651 fn start_session(_: SessionIndex) {652 // we don't care.653 }654 fn end_session(_: SessionIndex) {655 // we don't care.656 }657 }658}1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617// Original license:18// Copyright (C) 2021 Parity Technologies (UK) Ltd.19// SPDX-License-Identifier: Apache-2.02021// Licensed under the Apache License, Version 2.0 (the "License");22// you may not use this file except in compliance with the License.23// You may obtain a copy of the License at24//25// http://www.apache.org/licenses/LICENSE-2.026//27// Unless required by applicable law or agreed to in writing, software28// distributed under the License is distributed on an "AS IS" BASIS,29// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.30// See the License for the specific language governing permissions and31// limitations under the License.3233// todo:collator documentation34//! Collator Selection pallet.35//!36//! A pallet to manage collators in a parachain.37//!38//! ## Overview39//!40//! The Collator Selection pallet manages the collators of a parachain. **Collation is _not_ a41//! secure activity** and this pallet does not implement any game-theoretic mechanisms to meet BFT42//! safety assumptions of the chosen set.43//!44//! ## Terminology45//!46//! - Collator: A parachain block producer.47//! - Bond: An amount of `Balance` _reserved_ for candidate registration.48//! - Invulnerable: An account guaranteed to be in the collator set.49//!50//! ## Implementation51//!52//! The final `Collators` are aggregated from two individual lists:53//!54//! 1. [`Invulnerables`]: a set of collators appointed by governance. These accounts will always be55//! collators.56//! 2. [`Candidates`]: these are *candidates to the collation task* and may or may not be elected as57//! a final collator.58//!59//! The current implementation resolves congestion of [`Candidates`] in a first-come-first-serve60//! manner.61//!62//! Candidates will not be allowed to get kicked or leave_intent if the total number of candidates63//! fall below MinCandidates. This is for potential disaster recovery scenarios.64//!65//! ### Rewards66//!67//! The Collator Selection pallet maintains an on-chain account (the "Pot"). In each block, the68//! collator who authored it receives:69//!70//! - Half the value of the Pot.71//! - Half the value of the transaction fees within the block. The other half of the transaction72//! fees are deposited into the Pot.73//!74//! To initiate rewards an ED needs to be transferred to the pot address.75//!76//! Note: Eventually the Pot distribution may be modified as discussed in77//! [this issue](https://github.com/paritytech/statemint/issues/21#issuecomment-810481073).7879#![cfg_attr(not(feature = "std"), no_std)]8081pub use pallet::*;8283#[cfg(test)]84mod mock;8586#[cfg(test)]87mod tests;8889#[cfg(feature = "runtime-benchmarks")]90mod benchmarking;91pub mod weights;9293use frame_support::traits::fungible::Inspect;9495type BalanceOf<T> =96 <<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance;97#[frame_support::pallet]98pub mod pallet {99 use super::*;100 pub use crate::weights::WeightInfo;101 use core::ops::Div;102 use frame_support::{103 dispatch::{DispatchClass, DispatchResultWithPostInfo},104 inherent::Vec,105 pallet_prelude::*,106 sp_runtime::traits::{AccountIdConversion, CheckedSub, Saturating, Zero},107 traits::{108 EnsureOrigin,109 fungible::{Balanced, BalancedHold, Inspect, InspectHold, Mutate, MutateHold},110 ValidatorRegistration,111 tokens::{Precision, Preservation},112 },113 BoundedVec, PalletId,114 };115 use frame_system::pallet_prelude::*;116 use pallet_session::SessionManager;117 use sp_runtime::{Perbill, traits::Convert};118 use sp_staking::SessionIndex;119120 /// A convertor from collators id. Since this pallet does not have stash/controller, this is121 /// just identity.122 pub struct IdentityCollator;123 impl<T> sp_runtime::traits::Convert<T, Option<T>> for IdentityCollator {124 fn convert(t: T) -> Option<T> {125 Some(t)126 }127 }128129 /// Configure the pallet by specifying the parameters and types on which it depends.130 #[pallet::config]131 pub trait Config: frame_system::Config {132 /// Overarching event type.133 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;134 type Currency: Mutate<Self::AccountId>135 + MutateHold<Self::AccountId>136 + BalancedHold<Self::AccountId>;137138 /// Origin that can dictate updating parameters of this pallet.139 type UpdateOrigin: EnsureOrigin<Self::RuntimeOrigin>;140141 /// Account Identifier that holds the chain's treasury.142 type TreasuryAccountId: Get<Self::AccountId>;143144 /// Account Identifier from which the internal Pot is generated.145 type PotId: Get<PalletId>;146147 /// Maximum number of candidates and invulnerables that we should have. This is enforced in code.148 type MaxCollators: Get<u32>;149150 /// If kicked, how much of the collator's deposit will be slashed and sent to the slash destination.151 type SlashRatio: Get<Perbill>;152153 /// A stable ID for a validator.154 type ValidatorId: Member + Parameter;155156 /// A conversion from account ID to validator ID.157 ///158 /// Its cost must be at most one storage read.159 type ValidatorIdOf: Convert<Self::AccountId, Option<Self::ValidatorId>>;160161 /// Validate a user is registered162 type ValidatorRegistration: ValidatorRegistration<Self::ValidatorId>;163164 /// The weight information of this pallet.165 type WeightInfo: WeightInfo;166167 #[pallet::constant]168 type LicenceBondIdentifier: Get<<Self::Currency as InspectHold<Self::AccountId>>::Reason>;169170 type DesiredCollators: Get<u32>;171172 type LicenseBond: Get<BalanceOf<Self>>;173174 type KickThreshold: Get<Self::BlockNumber>;175 }176177 #[pallet::pallet]178 pub struct Pallet<T>(_);179180 /// The invulnerable, fixed collators.181 #[pallet::storage]182 #[pallet::getter(fn invulnerables)]183 pub type Invulnerables<T: Config> =184 StorageValue<_, BoundedVec<T::AccountId, T::MaxCollators>, ValueQuery>;185186 /// The (community) collation license holders.187 #[pallet::storage]188 #[pallet::getter(fn license_deposit_of)]189 pub type LicenseDepositOf<T: Config> =190 StorageMap<_, Blake2_128Concat, T::AccountId, BalanceOf<T>, ValueQuery>;191192 /// The (community, limited) collation candidates.193 #[pallet::storage]194 #[pallet::getter(fn candidates)]195 pub type Candidates<T: Config> =196 StorageValue<_, BoundedVec<T::AccountId, T::MaxCollators>, ValueQuery>;197198 /// Last block authored by collator.199 #[pallet::storage]200 #[pallet::getter(fn last_authored_block)]201 pub type LastAuthoredBlock<T: Config> =202 StorageMap<_, Twox64Concat, T::AccountId, T::BlockNumber, ValueQuery>;203204 #[pallet::genesis_config]205 pub struct GenesisConfig<T: Config> {206 pub invulnerables: Vec<T::AccountId>,207 }208209 #[cfg(feature = "std")]210 impl<T: Config> Default for GenesisConfig<T> {211 fn default() -> Self {212 Self {213 invulnerables: Default::default(),214 }215 }216 }217218 #[pallet::genesis_build]219 impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {220 fn build(&self) {221 let duplicate_invulnerables = self222 .invulnerables223 .iter()224 .collect::<std::collections::BTreeSet<_>>();225 assert!(226 duplicate_invulnerables.len() == self.invulnerables.len(),227 "duplicate invulnerables in genesis."228 );229230 let bounded_invulnerables =231 BoundedVec::<_, T::MaxCollators>::try_from(self.invulnerables.clone())232 .expect("genesis invulnerables are more than T::MaxCollators");233234 <Invulnerables<T>>::put(bounded_invulnerables);235 }236 }237238 #[pallet::event]239 #[pallet::generate_deposit(pub(super) fn deposit_event)]240 pub enum Event<T: Config> {241 InvulnerableAdded {242 invulnerable: T::AccountId,243 },244 InvulnerableRemoved {245 invulnerable: T::AccountId,246 },247 LicenseObtained {248 account_id: T::AccountId,249 deposit: BalanceOf<T>,250 },251 LicenseReleased {252 account_id: T::AccountId,253 deposit_returned: BalanceOf<T>,254 },255 CandidateAdded {256 account_id: T::AccountId,257 },258 CandidateRemoved {259 account_id: T::AccountId,260 },261 }262263 // Errors inform users that something went wrong.264 #[pallet::error]265 pub enum Error<T> {266 /// Too many candidates267 TooManyCandidates,268 /// Unknown error269 Unknown,270 /// Permission issue271 Permission,272 /// User already holds license to collate273 AlreadyHoldingLicense,274 /// User does not hold a license to collate275 NoLicense,276 /// User is already a candidate277 AlreadyCandidate,278 /// User is not a candidate279 NotCandidate,280 /// Too many invulnerables281 TooManyInvulnerables,282 /// Too few invulnerables283 TooFewInvulnerables,284 /// User is already an Invulnerable285 AlreadyInvulnerable,286 /// User is not an Invulnerable287 NotInvulnerable,288 /// Account has no associated validator ID289 NoAssociatedValidatorId,290 /// Validator ID is not yet registered291 ValidatorNotRegistered,292 }293294 #[pallet::hooks]295 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}296297 #[pallet::call]298 impl<T: Config> Pallet<T> {299 /// Add a collator to the list of invulnerable (fixed) collators.300 #[pallet::call_index(0)]301 #[pallet::weight(<T as Config>::WeightInfo::add_invulnerable(T::MaxCollators::get()))]302 pub fn add_invulnerable(303 origin: OriginFor<T>,304 new: T::AccountId,305 ) -> DispatchResultWithPostInfo {306 T::UpdateOrigin::ensure_origin(origin)?;307308 // check if the new invulnerable has associated validator keys before it is added309 let validator_key = T::ValidatorIdOf::convert(new.clone())310 .ok_or(Error::<T>::NoAssociatedValidatorId)?;311 ensure!(312 T::ValidatorRegistration::is_registered(&validator_key),313 Error::<T>::ValidatorNotRegistered314 );315 if Self::invulnerables().contains(&new) {316 return Ok(().into());317 }318319 <Invulnerables<T>>::try_append(new.clone())320 .map_err(|_| Error::<T>::TooManyInvulnerables)?;321322 // try to offboard the new invulnerable if it was a collator candidate before323 let _ = Self::try_remove_candidate(&new);324325 Self::deposit_event(Event::InvulnerableAdded { invulnerable: new });326 Ok(().into())327 }328329 /// Remove a collator from the list of invulnerable (fixed) collators.330 #[pallet::call_index(1)]331 #[pallet::weight(<T as Config>::WeightInfo::remove_invulnerable(T::MaxCollators::get()))]332 pub fn remove_invulnerable(333 origin: OriginFor<T>,334 who: T::AccountId,335 ) -> DispatchResultWithPostInfo {336 T::UpdateOrigin::ensure_origin(origin)?;337338 <Invulnerables<T>>::try_mutate(|invulnerables| -> DispatchResult {339 if invulnerables.len() <= 1 {340 return Err(Error::<T>::TooFewInvulnerables.into());341 }342343 let index = invulnerables344 .into_iter()345 .position(|r| *r == who)346 .ok_or(Error::<T>::NotInvulnerable)?;347 invulnerables.remove(index);348 Ok(())349 })?;350 Self::deposit_event(Event::InvulnerableRemoved { invulnerable: who });351 Ok(().into())352 }353354 /// Purchase a license on block collation for this account.355 /// It does not make it a collator candidate, use `onboard` afterward. The account must356 /// (a) already have registered session keys and (b) be able to reserve the `LicenseBond`.357 ///358 /// This call is not available to `Invulnerable` collators.359 #[pallet::call_index(2)]360 #[pallet::weight(<T as Config>::WeightInfo::get_license(T::MaxCollators::get()))]361 pub fn get_license(origin: OriginFor<T>) -> DispatchResultWithPostInfo {362 // register_as_candidate363 let who = ensure_signed(origin)?;364365 if LicenseDepositOf::<T>::contains_key(&who) {366 return Err(Error::<T>::AlreadyHoldingLicense.into());367 }368369 let validator_key = T::ValidatorIdOf::convert(who.clone())370 .ok_or(Error::<T>::NoAssociatedValidatorId)?;371 ensure!(372 T::ValidatorRegistration::is_registered(&validator_key),373 Error::<T>::ValidatorNotRegistered374 );375376 let deposit = T::LicenseBond::get();377378 T::Currency::hold(&T::LicenceBondIdentifier::get(), &who, deposit)?;379 LicenseDepositOf::<T>::insert(who.clone(), deposit);380381 Self::deposit_event(Event::LicenseObtained {382 account_id: who,383 deposit,384 });385 Ok(().into()) // Some(T::WeightInfo::register_as_candidate(current_count as u32)).into())386 }387388 /// Register this account as a candidate for collators for next sessions.389 /// The account must already hold a license, and cannot offboard immediately during a session.390 ///391 /// This call is not available to `Invulnerable` collators.392 #[pallet::call_index(3)]393 #[pallet::weight(<T as Config>::WeightInfo::onboard(T::MaxCollators::get()))]394 pub fn onboard(origin: OriginFor<T>) -> DispatchResultWithPostInfo {395 // register_as_candidate396 let who = ensure_signed(origin)?;397398 // ensure the user obtained the license.399 ensure!(400 LicenseDepositOf::<T>::contains_key(&who),401 Error::<T>::NoLicense402 );403 // ensure we are below limit.404 let length = <Candidates<T>>::decode_len().unwrap_or_default()405 + <Invulnerables<T>>::decode_len().unwrap_or_default();406 ensure!(407 (length as u32) < T::DesiredCollators::get(),408 Error::<T>::TooManyCandidates409 );410 ensure!(411 !Self::invulnerables().contains(&who),412 Error::<T>::AlreadyInvulnerable413 );414415 let current_count =416 <Candidates<T>>::try_mutate(|candidates| -> Result<usize, DispatchError> {417 if candidates.iter().any(|candidate| *candidate == who) {418 Err(Error::<T>::AlreadyCandidate)?419 } else {420 candidates421 .try_push(who.clone())422 .map_err(|_| Error::<T>::TooManyCandidates)?;423 // First authored block is current block plus kick threshold to handle session delay424 <LastAuthoredBlock<T>>::insert(425 who.clone(),426 frame_system::Pallet::<T>::block_number() + T::KickThreshold::get(),427 );428 Ok(candidates.len())429 }430 })?;431432 Self::deposit_event(Event::CandidateAdded { account_id: who });433 Ok(Some(<T as Config>::WeightInfo::onboard(current_count as u32)).into())434 }435436 /// Deregister `origin` as a collator candidate. Note that the collator can only leave on437 /// session change. The license to `onboard` later at any other time will remain.438 #[pallet::call_index(4)]439 #[pallet::weight(<T as Config>::WeightInfo::offboard(T::MaxCollators::get()))]440 pub fn offboard(origin: OriginFor<T>) -> DispatchResultWithPostInfo {441 // leave_intent442 let who = ensure_signed(origin)?;443 let current_count = Self::try_remove_candidate(&who)?;444445 Ok(Some(<T as Config>::WeightInfo::offboard(current_count as u32)).into())446 }447448 /// Forfeit `origin`'s own license. The `LicenseBond` will be unreserved immediately.449 ///450 /// This call is not available to `Invulnerable` collators.451 #[pallet::call_index(5)]452 #[pallet::weight(<T as Config>::WeightInfo::release_license(T::MaxCollators::get()))]453 pub fn release_license(origin: OriginFor<T>) -> DispatchResultWithPostInfo {454 // leave_intent455 let who = ensure_signed(origin)?;456457 let current_count = Self::try_remove_candidate_and_release_license(&who, false, true)?;458459 Ok(Some(<T as Config>::WeightInfo::release_license(460 current_count as u32,461 ))462 .into())463 }464465 /// Force deregister `origin` as a collator candidate as a governing authority, and revoke its license.466 /// Note that the collator can only leave on session change.467 /// The `LicenseBond` will be unreserved and returned immediately.468 ///469 /// This call is, of course, not applicable to `Invulnerable` collators.470 #[pallet::call_index(6)]471 #[pallet::weight(<T as Config>::WeightInfo::force_release_license(T::MaxCollators::get()))]472 pub fn force_release_license(473 origin: OriginFor<T>,474 who: T::AccountId,475 ) -> DispatchResultWithPostInfo {476 // leave_intent477 T::UpdateOrigin::ensure_origin(origin)?;478479 let current_count = Self::try_remove_candidate_and_release_license(&who, false, true)?;480481 Ok(Some(<T as Config>::WeightInfo::force_release_license(482 current_count as u32,483 ))484 .into())485 }486 }487488 impl<T: Config> Pallet<T> {489 /// Get a unique, inaccessible account id from the `PotId`.490 pub fn account_id() -> T::AccountId {491 T::PotId::get().into_account_truncating()492 }493494 /// Removes a candidate and their license, optionally slashed and optionally ignoring,495 /// whether or not they actually are a candidate.496 fn try_remove_candidate_and_release_license(497 who: &T::AccountId,498 should_slash: bool,499 ignore_if_not_candidate: bool,500 ) -> Result<usize, DispatchError> {501 let current_count = Self::try_remove_candidate(who);502 let current_count = if ignore_if_not_candidate503 && current_count == Err(Error::<T>::NotCandidate.into())504 {505 <Candidates<T>>::decode_len().unwrap_or_default()506 } else {507 current_count?508 };509 Self::try_release_license(who, should_slash)?;510 Ok(current_count)511 }512513 /// Removes a candidate from the collator pool for the next session if they exist.514 fn try_remove_candidate(who: &T::AccountId) -> Result<usize, DispatchError> {515 let current_count =516 <Candidates<T>>::try_mutate(|candidates| -> Result<usize, DispatchError> {517 let index = candidates518 .iter()519 .position(|candidate| *candidate == *who)520 .ok_or(Error::<T>::NotCandidate)?;521 candidates.remove(index);522 <LastAuthoredBlock<T>>::remove(who.clone());523 Ok(candidates.len())524 })?;525 Self::deposit_event(Event::CandidateRemoved {526 account_id: who.clone(),527 });528 Ok(current_count)529 }530531 /// Removes a candidate if they exist and sends them back their deposit, optionally slashed.532 fn try_release_license(who: &T::AccountId, should_slash: bool) -> DispatchResult {533 let mut deposit_returned = BalanceOf::<T>::default();534 LicenseDepositOf::<T>::try_mutate_exists(who, |deposit| -> DispatchResult {535 if let Some(deposit) = deposit.take() {536 if should_slash {537 let slashed = T::SlashRatio::get() * deposit;538 let remaining = deposit - slashed;539540 let (imbalance, _) =541 T::Currency::slash(&T::LicenceBondIdentifier::get(), who, slashed);542 deposit_returned = remaining;543544 T::Currency::resolve(&T::TreasuryAccountId::get(), imbalance)545 .map_err(|_| DispatchError::Other("Failed to deposit imbalance"))?;546 } else {547 deposit_returned = deposit;548 }549550 T::Currency::release(551 &T::LicenceBondIdentifier::get(),552 who,553 deposit_returned,554 Precision::Exact,555 )?;556 Ok(())557 } else {558 Err(Error::<T>::NoLicense.into())559 }560 })?;561 Self::deposit_event(Event::LicenseReleased {562 account_id: who.clone(),563 deposit_returned,564 });565 Ok(())566 }567568 /// Assemble the current set of candidates and invulnerables into the next collator set.569 ///570 /// This is done on the fly, as frequent as we are told to do so, as the session manager.571 pub fn assemble_collators(572 candidates: BoundedVec<T::AccountId, T::MaxCollators>,573 ) -> Vec<T::AccountId> {574 let mut collators = Self::invulnerables().to_vec();575 collators.extend(candidates);576 collators577 }578579 /// Kicks out candidates that did not produce a block in the kick threshold580 /// and **confiscates** their deposits to the treasury.581 pub fn kick_stale_candidates(582 candidates: BoundedVec<T::AccountId, T::MaxCollators>,583 ) -> BoundedVec<T::AccountId, T::MaxCollators> {584 let now = frame_system::Pallet::<T>::block_number();585 let kick_threshold = T::KickThreshold::get();586 candidates587 .into_iter()588 .filter_map(|c| {589 let last_block = <LastAuthoredBlock<T>>::get(c.clone());590 let since_last = now.saturating_sub(last_block);591 if since_last < kick_threshold {592 Some(c)593 } else {594 let outcome = Self::try_remove_candidate_and_release_license(&c, true, false);595 if let Err(why) = outcome {596 log::warn!("Failed to kick collator and release license {:?}", why);597 debug_assert!(false, "failed to kick collator and release license {why:?}");598 }599 None600 }601 })602 .collect::<Vec<_>>()603 .try_into()604 .expect("filter_map operation can't result in a bounded vec larger than its original; qed")605 }606 }607608 /// Keep track of number of authored blocks per authority, uncles are counted as well since609 /// they're a valid proof of being online.610 impl<T: Config + pallet_authorship::Config>611 pallet_authorship::EventHandler<T::AccountId, T::BlockNumber> for Pallet<T>612 {613 fn note_author(author: T::AccountId) {614 let pot = Self::account_id();615 // assumes an ED will be sent to pot.616 let reward = T::Currency::balance(&pot)617 .checked_sub(&T::Currency::minimum_balance())618 .unwrap_or_else(Zero::zero)619 .div(2u32.into());620621 if !reward.is_zero() {622 // `reward` is half of pot account minus ED, this should never fail.623 let _success = T::Currency::transfer(&pot, &author, reward, Preservation::Preserve);624 debug_assert!(_success.is_ok());625 }626 <LastAuthoredBlock<T>>::insert(author, frame_system::Pallet::<T>::block_number());627628 frame_system::Pallet::<T>::register_extra_weight_unchecked(629 <T as Config>::WeightInfo::note_author(),630 DispatchClass::Mandatory,631 );632 }633 }634635 /// Play the role of the session manager.636 impl<T: Config> SessionManager<T::AccountId> for Pallet<T> {637 fn new_session(index: SessionIndex) -> Option<Vec<T::AccountId>> {638 log::info!(639 "assembling new collators for new session {} at #{:?}",640 index,641 <frame_system::Pallet<T>>::block_number(),642 );643644 let candidates = Self::candidates();645 let candidates_len_before = candidates.len();646 let active_candidates = Self::kick_stale_candidates(candidates);647 let removed = candidates_len_before - active_candidates.len();648 let result = Self::assemble_collators(active_candidates);649650 frame_system::Pallet::<T>::register_extra_weight_unchecked(651 <T as Config>::WeightInfo::new_session(652 candidates_len_before as u32,653 removed as u32,654 ),655 DispatchClass::Mandatory,656 );657 Some(result)658 }659 fn start_session(_: SessionIndex) {660 // we don't care.661 }662 fn end_session(_: SessionIndex) {663 // we don't care.664 }665 }666}pallets/collator-selection/src/mock.rsdiffbeforeafterboth--- a/pallets/collator-selection/src/mock.rs
+++ b/pallets/collator-selection/src/mock.rs
@@ -63,7 +63,6 @@
Balances: pallet_balances::{Pallet, Call, Storage, Config<T>, Event<T>},
CollatorSelection: collator_selection::{Pallet, Call, Storage, Event<T>},
Authorship: pallet_authorship::{Pallet, Storage},
- Configuration: pallet_configuration::{Pallet, Call, Storage, Event<T>},
}
);
@@ -216,21 +215,6 @@
pub AppPromotionDailyRate: Perbill = Perbill::from_rational(5u32, 10_000);
pub const DayRelayBlocks: u32 = 1;
pub const LicenceBondIdentifier: [u8; 16] = *b"licenceidentifie";
-}
-
-impl pallet_configuration::Config for Test {
- type RuntimeEvent = RuntimeEvent;
- type Currency = Balances;
- type DefaultCollatorSelectionMaxCollators = MaxCollators;
- type DefaultCollatorSelectionKickThreshold = KickThreshold;
- type DefaultCollatorSelectionLicenseBond = LicenseBond;
- // the following constants we don't care about
- type DefaultWeightToFeeCoefficient = DefaultWeightToFeeCoefficient;
- type DefaultMinGasPrice = DefaultMinGasPrice;
- type MaxXcmAllowedLocations = MaxXcmAllowedLocations;
- type AppPromotionDailyRate = AppPromotionDailyRate;
- type DayRelayBlocks = DayRelayBlocks;
- type WeightInfo = pallet_configuration::weights::SubstrateWeight<Self>;
}
ord_parameter_types! {
@@ -246,11 +230,7 @@
pub struct IsRegistered;
impl ValidatorRegistration<u64> for IsRegistered {
fn is_registered(id: &u64) -> bool {
- if *id == 7u64 {
- false
- } else {
- true
- }
+ *id != 7u64
}
}
@@ -265,6 +245,10 @@
type ValidatorIdOf = IdentityCollator;
type ValidatorRegistration = IsRegistered;
type LicenceBondIdentifier = LicenceBondIdentifier;
+ type Currency = Balances;
+ type DesiredCollators = MaxCollators;
+ type LicenseBond = LicenseBond;
+ type KickThreshold = KickThreshold;
type WeightInfo = ();
}
@@ -277,7 +261,11 @@
let ed = <Test as pallet_balances::Config>::ExistentialDeposit::get();
- let balances = vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100), (33, ed)];
+ let balances: Vec<(u64, u64)> = (1..=<Test as Config>::DesiredCollators::get() as u64 + 1)
+ .map(|i| (i, 100))
+ .chain(core::iter::once((33, ed)))
+ .collect();
+
let keys = balances
.iter()
.map(|&(i, _)| {
pallets/collator-selection/src/tests.rsdiffbeforeafterboth--- a/pallets/collator-selection/src/tests.rs
+++ b/pallets/collator-selection/src/tests.rs
@@ -30,19 +30,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate as collator_selection;
+use crate::{self as collator_selection, Config};
use crate::{mock::*, Error};
use frame_support::{
assert_noop, assert_ok,
traits::{fungible, GenesisBuild, OnInitialize},
};
-use frame_system::RawOrigin;
use sp_runtime::{traits::BadOrigin, TokenError};
-use pallet_configuration::{
- CollatorSelectionDesiredCollatorsOverride as DesiredCollators,
- CollatorSelectionKickThresholdOverride as KickThreshold,
- CollatorSelectionLicenseBondOverride as LicenseBond,
-};
use scale_info::prelude::*;
fn get_license_and_onboard(account_id: <Test as frame_system::Config>::AccountId) {
@@ -57,9 +51,6 @@
#[test]
fn basic_setup_works() {
new_test_ext().execute_with(|| {
- assert_eq!(<DesiredCollators<Test>>::get(), 5);
- assert_eq!(<LicenseBond<Test>>::get(), 10);
-
assert!(CollatorSelection::candidates().is_empty());
assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
});
@@ -120,51 +111,6 @@
assert_noop!(
CollatorSelection::remove_invulnerable(RuntimeOrigin::signed(RootAccount::get()), 1),
Error::<Test>::TooFewInvulnerables
- );
- });
-}
-
-#[test]
-fn set_desired_collators_works() {
- new_test_ext().execute_with(|| {
- // given
- assert_eq!(<DesiredCollators<Test>>::get(), 5);
-
- // can set
- assert_ok!(Configuration::set_collator_selection_desired_collators(
- RawOrigin::Root.into(),
- Some(7)
- ));
- assert_eq!(<DesiredCollators<Test>>::get(), 7);
-
- // rejects bad origin
- assert_noop!(
- Configuration::set_collator_selection_desired_collators(
- RuntimeOrigin::signed(1),
- Some(8)
- ),
- BadOrigin
- );
- });
-}
-
-#[test]
-fn set_license_bond() {
- new_test_ext().execute_with(|| {
- // given
- assert_eq!(<LicenseBond<Test>>::get(), 10);
-
- // can set
- assert_ok!(Configuration::set_collator_selection_license_bond(
- RawOrigin::Root.into(),
- Some(7)
- ));
- assert_eq!(<LicenseBond<Test>>::get(), 7);
-
- // rejects bad origin.
- assert_noop!(
- Configuration::set_collator_selection_license_bond(RuntimeOrigin::signed(1), Some(8)),
- BadOrigin
);
});
}
@@ -187,26 +133,19 @@
#[test]
fn cannot_onboard_candidate_if_too_many() {
new_test_ext().execute_with(|| {
- // reset desired candidates
- <pallet_configuration::CollatorSelectionDesiredCollatorsOverride<Test>>::put(0);
-
- // can still get a license.
- assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(4)));
-
- // can't accept anyone anymore.
- assert_noop!(
- CollatorSelection::onboard(RuntimeOrigin::signed(4)),
- Error::<Test>::TooManyCandidates,
- );
-
- // reset desired candidates to invulnerables + 1
- <pallet_configuration::CollatorSelectionDesiredCollatorsOverride<Test>>::put(3);
- assert_ok!(CollatorSelection::onboard(RuntimeOrigin::signed(4)));
+ // can accept desired value of collators.
+ for c in 3u64..=(<Test as Config>::DesiredCollators::get()).into() {
+ assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(c)));
+ assert_ok!(CollatorSelection::onboard(RuntimeOrigin::signed(c)));
+ }
// but no more.
- assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(5)));
+ let undesired_collator = (<Test as Config>::DesiredCollators::get() + 1) as u64;
+ assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(
+ undesired_collator
+ )));
assert_noop!(
- CollatorSelection::onboard(RuntimeOrigin::signed(5)),
+ CollatorSelection::onboard(RuntimeOrigin::signed(undesired_collator)),
Error::<Test>::TooManyCandidates,
);
})
@@ -227,8 +166,8 @@
fn cannot_obtain_license_if_poor() {
new_test_ext().execute_with(|| {
let ed = <Test as pallet_balances::Config>::ExistentialDeposit::get();
- assert_eq!(Balances::free_balance(&3), 100);
- assert_eq!(Balances::free_balance(&33), ed);
+ assert_eq!(Balances::free_balance(3), 100);
+ assert_eq!(Balances::free_balance(33), ed);
// works
assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));
@@ -266,21 +205,18 @@
#[test]
fn becoming_candidate_works() {
new_test_ext().execute_with(|| {
- // given
- assert_eq!(<DesiredCollators<Test>>::get(), 5);
- assert_eq!(<LicenseBond<Test>>::get(), 10);
assert_eq!(CollatorSelection::candidates(), Vec::new());
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(3), 100);
+ assert_eq!(Balances::free_balance(4), 100);
get_license_and_onboard(3);
get_license_and_onboard(4);
- assert_eq!(Balances::free_balance(&3), 90);
- assert_eq!(Balances::free_balance(&4), 90);
+ assert_eq!(Balances::free_balance(3), 90);
+ assert_eq!(Balances::free_balance(4), 90);
assert_eq!(CollatorSelection::candidates().len(), 2);
});
@@ -513,7 +449,7 @@
assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3, 4]);
assert_eq!(CollatorSelection::candidates(), vec![4]);
- assert_eq!(<KickThreshold<Test>>::get(), 10);
+ // assert_eq!(<KickThreshold<Test>>::get(), 10);
assert_eq!(CollatorSelection::last_authored_block(4), 20);
initialize_to_block(30);
pallets/configuration/CHANGELOG.mddiffbeforeafterboth--- a/pallets/configuration/CHANGELOG.md
+++ b/pallets/configuration/CHANGELOG.md
@@ -3,7 +3,12 @@
All notable changes to this project will be documented in this file.
<!-- bureaucrate goes here -->
+## [0.2.0] - 2023-06-27
+
+### Major change
+- Architecture fixed: in the configuration of this pallet, bounds on the associated type were determined not by the functional requirements for this pallet itself, but by the pallet that had tight coupling with it.
+
## [0.1.3] - 2022-12-29
### Added
@@ -20,4 +25,4 @@
### Other changes
-- build: Upgrade polkadot to v0.9.27 2c498572636f2b34d53b1c51b7283a761a7dc90a
\ No newline at end of file
+- build: Upgrade polkadot to v0.9.27 2c498572636f2b34d53b1c51b7283a761a7dc90a
pallets/configuration/Cargo.tomldiffbeforeafterboth--- a/pallets/configuration/Cargo.toml
+++ b/pallets/configuration/Cargo.toml
@@ -1,7 +1,7 @@
[package]
edition = "2021"
name = "pallet-configuration"
-version = "0.1.3"
+version = "0.2.0"
[dependencies]
# Note: `package = "parity-scale-codec"` must be supplied since the `Encode` macro searches for it.
@@ -22,11 +22,11 @@
default = ["std"]
runtime-benchmarks = ["frame-benchmarking"]
std = [
+ "codec/std",
"fp-evm/std",
"frame-benchmarking/std",
"frame-support/std",
"frame-system/std",
- "codec/std",
"sp-arithmetic/std",
"sp-core/std",
"sp-std/std",
pallets/configuration/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/configuration/src/benchmarking.rs
+++ b/pallets/configuration/src/benchmarking.rs
@@ -19,7 +19,7 @@
use super::*;
use frame_benchmarking::benchmarks;
use frame_system::{EventRecord, RawOrigin};
-use frame_support::{assert_ok, traits::fungible::Inspect};
+use frame_support::assert_ok;
fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
let events = frame_system::Pallet::<T>::events();
@@ -30,7 +30,10 @@
}
benchmarks! {
- where_clause { where T: Config }
+ where_clause { where
+ T: Config,
+ T::Balance: From<u32>
+ }
set_weight_to_fee_coefficient_override {
let coeff: u64 = 999;
@@ -60,7 +63,7 @@
let max: u32 = 999;
}: {
assert_ok!(
- <Pallet<T>>::set_collator_selection_desired_collators(RawOrigin::Root.into(), Some(max.clone()))
+ <Pallet<T>>::set_collator_selection_desired_collators(RawOrigin::Root.into(), Some(max))
);
}
verify {
@@ -68,10 +71,10 @@
}
set_collator_selection_license_bond {
- let bond_cost: Option<BalanceOf<T>> = Some(T::Currency::minimum_balance() * 10u32.into());
+ let bond_cost: Option<T::Balance> = Some(1000u32.into());
}: {
assert_ok!(
- <Pallet<T>>::set_collator_selection_license_bond(RawOrigin::Root.into(), bond_cost.clone())
+ <Pallet<T>>::set_collator_selection_license_bond(RawOrigin::Root.into(), bond_cost)
);
}
verify {
@@ -82,7 +85,7 @@
let threshold: Option<T::BlockNumber> = Some(900u32.into());
}: {
assert_ok!(
- <Pallet<T>>::set_collator_selection_kick_threshold(RawOrigin::Root.into(), threshold.clone())
+ <Pallet<T>>::set_collator_selection_kick_threshold(RawOrigin::Root.into(), threshold)
);
}
verify {
pallets/configuration/src/lib.rsdiffbeforeafterboth--- a/pallets/configuration/src/lib.rs
+++ b/pallets/configuration/src/lib.rs
@@ -22,6 +22,7 @@
pallet,
weights::{WeightToFeePolynomial, WeightToFeeCoefficients, WeightToFeeCoefficient, Weight},
traits::Get,
+ Parameter,
};
use codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
@@ -42,27 +43,33 @@
mod pallet {
use super::*;
use frame_support::{
- traits::{fungible, Get},
- pallet_prelude::{StorageValue, ValueQuery, DispatchResult, IsType},
+ traits::{Get},
+ pallet_prelude::{
+ StorageValue, ValueQuery, DispatchResult, IsType, Member, MaybeSerializeDeserialize,
+ },
log,
+ dispatch::{Codec, fmt::Debug},
};
- use frame_system::{pallet_prelude::OriginFor, ensure_root, Config as SystemConfig};
-
+ use frame_system::{pallet_prelude::OriginFor, ensure_root};
+ use sp_arithmetic::{FixedPointOperand, traits::AtLeast32BitUnsigned};
pub use crate::weights::WeightInfo;
- pub type BalanceOf<T> =
- <<T as Config>::Currency as fungible::Inspect<<T as SystemConfig>::AccountId>>::Balance;
#[pallet::config]
pub trait Config: frame_system::Config {
/// Overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
- type Currency: fungible::Inspect<Self::AccountId>
- + fungible::Mutate<Self::AccountId>
- + fungible::MutateFreeze<Self::AccountId>
- + fungible::InspectHold<Self::AccountId>
- + fungible::MutateHold<Self::AccountId>
- + fungible::BalancedHold<Self::AccountId>;
+ type Balance: Parameter
+ + Member
+ + AtLeast32BitUnsigned
+ + Codec
+ + Default
+ + Copy
+ + MaybeSerializeDeserialize
+ + Debug
+ + MaxEncodedLen
+ + TypeInfo
+ + FixedPointOperand;
#[pallet::constant]
type DefaultWeightToFeeCoefficient: Get<u64>;
@@ -79,7 +86,7 @@
#[pallet::constant]
type DefaultCollatorSelectionMaxCollators: Get<u32>;
#[pallet::constant]
- type DefaultCollatorSelectionLicenseBond: Get<BalanceOf<Self>>;
+ type DefaultCollatorSelectionLicenseBond: Get<Self::Balance>;
#[pallet::constant]
type DefaultCollatorSelectionKickThreshold: Get<Self::BlockNumber>;
@@ -94,7 +101,7 @@
desired_collators: Option<u32>,
},
NewCollatorLicenseBond {
- bond_cost: Option<BalanceOf<T>>,
+ bond_cost: Option<T::Balance>,
},
NewCollatorKickThreshold {
length_in_blocks: Option<T::BlockNumber>,
@@ -130,7 +137,7 @@
#[pallet::storage]
pub type CollatorSelectionLicenseBondOverride<T: Config> = StorageValue<
- Value = BalanceOf<T>,
+ Value = T::Balance,
QueryKind = ValueQuery,
OnEmpty = T::DefaultCollatorSelectionLicenseBond,
>;
@@ -221,7 +228,7 @@
#[pallet::weight(T::WeightInfo::set_collator_selection_license_bond())]
pub fn set_collator_selection_license_bond(
origin: OriginFor<T>,
- amount: Option<BalanceOf<T>>,
+ amount: Option<<T as Config>::Balance>,
) -> DispatchResult {
ensure_root(origin)?;
if let Some(amount) = amount {
runtime/common/config/pallets/collator_selection.rsdiffbeforeafterboth--- a/runtime/common/config/pallets/collator_selection.rs
+++ b/runtime/common/config/pallets/collator_selection.rs
@@ -23,7 +23,10 @@
};
use sp_runtime::Perbill;
use up_common::constants::{UNIQUE, MILLIUNIQUE};
-
+use pallet_configuration::{
+ CollatorSelectionKickThresholdOverride, CollatorSelectionLicenseBondOverride,
+ CollatorSelectionDesiredCollatorsOverride,
+};
parameter_types! {
pub const SessionOffset: BlockNumber = 0;
}
@@ -60,6 +63,9 @@
pub const MaxAdditionalFields: u32 = 100;
pub const MaxRegistrars: u32 = 20;
pub const LicenceBondIdentifier: [u8; 16] = *b"licenceidentifie";
+ pub LicenseBond: Balance = CollatorSelectionLicenseBondOverride::<Runtime>::get();
+ pub DesiredCollators: u32 = CollatorSelectionDesiredCollatorsOverride::<Runtime>::get();
+ pub KickThreshold: BlockNumber = CollatorSelectionKickThresholdOverride::<Runtime>::get();
}
impl pallet_identity::Config for Runtime {
@@ -84,6 +90,7 @@
impl pallet_collator_selection::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
+ type Currency = Balances;
// We allow root only to execute privileged collator selection operations.
type UpdateOrigin = EnsureRoot<AccountId>;
type TreasuryAccountId = TreasuryAccountId;
@@ -95,4 +102,7 @@
type ValidatorRegistration = Session;
type WeightInfo = pallet_collator_selection::weights::SubstrateWeight<Runtime>;
type LicenceBondIdentifier = LicenceBondIdentifier;
+ type DesiredCollators = DesiredCollators;
+ type LicenseBond = LicenseBond;
+ type KickThreshold = KickThreshold;
}
runtime/common/config/pallets/mod.rsdiffbeforeafterboth--- a/runtime/common/config/pallets/mod.rs
+++ b/runtime/common/config/pallets/mod.rs
@@ -129,7 +129,7 @@
impl pallet_configuration::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
- type Currency = Balances;
+ type Balance = Balance;
type DefaultWeightToFeeCoefficient = ConstU64<{ up_common::constants::WEIGHT_TO_FEE_COEFF }>;
type DefaultMinGasPrice = ConstU64<{ up_common::constants::MIN_GAS_PRICE }>;
type DefaultCollatorSelectionMaxCollators = MaxCollators;
tests/src/benchmarks/mintFee/index.tsdiffbeforeafterboth--- a/tests/src/benchmarks/mintFee/index.ts
+++ b/tests/src/benchmarks/mintFee/index.ts
@@ -360,7 +360,7 @@
susbstrateReceiver.addressRaw,
PROPERTIES.slice(0, setup.propertiesNumber),
)
- .send({from: ethSigner, gas: 25_000_000});
+ .send({from: ethSigner});
},
);