difftreelog
Merge branch 'feature/switch-from-currecy-trait-to-fungible-v2' into feature/update-polkadot-v0.9.42
in: master
7 files changed
pallets/collator-selection/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/collator-selection/src/benchmarking.rs
+++ b/pallets/collator-selection/src/benchmarking.rs
@@ -40,7 +40,11 @@
use frame_support::{
assert_ok,
codec::Decode,
- traits::{Currency, EnsureOrigin, Get},
+ traits::{
+ EnsureOrigin,
+ fungible::{Inspect, Mutate},
+ Get,
+ },
};
use frame_system::{EventRecord, RawOrigin};
use pallet_authorship::EventHandler;
@@ -78,7 +82,7 @@
) -> T::AccountId {
let user = account(string, n, SEED);
let balance = balance_unit::<T>() * balance_factor.into();
- let _ = T::Currency::make_free_balance_be(&user, balance);
+ let _ = T::Currency::set_balance(&user, balance);
user
}
@@ -137,7 +141,7 @@
);
for who in candidates {
- T::Currency::make_free_balance_be(&who, <LicenseBond<T>>::get() * 2u32.into());
+ T::Currency::set_balance(&who, <LicenseBond<T>>::get() * 2u32.into());
<CollatorSelection<T>>::get_license(RawOrigin::Signed(who.clone()).into()).unwrap();
<CollatorSelection<T>>::onboard(RawOrigin::Signed(who).into()).unwrap();
}
@@ -153,14 +157,14 @@
);
for who in candidates {
- T::Currency::make_free_balance_be(&who, <LicenseBond<T>>::get() * 2u32.into());
+ T::Currency::set_balance(&who, <LicenseBond<T>>::get() * 2u32.into());
<CollatorSelection<T>>::get_license(RawOrigin::Signed(who.clone()).into()).unwrap();
}
}
/// `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>() -> <T::Currency as Currency<T::AccountId>>::Balance {
+fn balance_unit<T: Config>() -> BalanceOf<T> {
200u32.into()
}
@@ -168,7 +172,9 @@
const INITIAL_INVULNERABLES: u32 = 2;
benchmarks! {
- where_clause { where T: pallet_authorship::Config + session::Config + configuration::Config }
+ where_clause { where
+ T: pallet_authorship::Config + session::Config + configuration::Config
+ }
// todo:collator this and all the following do not work for some reason, going all the way up to 10 in length
// Both invulnerables and candidates count together against MaxCollators.
@@ -182,7 +188,7 @@
let new_invulnerable: T::AccountId = whitelisted_caller();
let bond: BalanceOf<T> = balance_unit::<T>() * 2u32.into();
- T::Currency::make_free_balance_be(&new_invulnerable, bond.clone());
+ T::Currency::set_balance(&new_invulnerable, bond.clone());
<session::Pallet<T>>::set_keys(
RawOrigin::Signed(new_invulnerable.clone()).into(),
@@ -227,7 +233,7 @@
let caller: T::AccountId = whitelisted_caller();
let bond: BalanceOf<T> = balance_unit::<T>() * 2u32.into();
- T::Currency::make_free_balance_be(&caller, bond.clone());
+ T::Currency::set_balance(&caller, bond.clone());
<session::Pallet<T>>::set_keys(
RawOrigin::Signed(caller.clone()).into(),
@@ -253,7 +259,7 @@
let caller: T::AccountId = whitelisted_caller();
let bond: BalanceOf<T> = balance_unit::<T>() * 2u32.into();
- T::Currency::make_free_balance_be(&caller, bond.clone());
+ T::Currency::set_balance(&caller, bond.clone());
let origin = RawOrigin::Signed(caller.clone());
@@ -329,7 +335,7 @@
// worst case is paying a non-existing candidate account.
note_author {
<LicenseBond<T>>::put(balance_unit::<T>());
- T::Currency::make_free_balance_be(
+ T::Currency::set_balance(
&<CollatorSelection<T>>::account_id(),
balance_unit::<T>() * 4u32.into(),
);
@@ -337,11 +343,11 @@
let new_block: T::BlockNumber = 10u32.into();
frame_system::Pallet::<T>::set_block_number(new_block);
- assert!(T::Currency::free_balance(&author) == 0u32.into());
+ assert!(T::Currency::balance(&author) == 0u32.into());
}: {
<CollatorSelection<T> as EventHandler<_, _>>::note_author(author.clone())
} verify {
- assert!(T::Currency::free_balance(&author) > 0u32.into());
+ assert!(T::Currency::balance(&author) > 0u32.into());
assert_eq!(frame_system::Pallet::<T>::block_number(), new_block);
}
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 pub use crate::weights::WeightInfo;96 use core::ops::Div;97 use frame_support::{98 dispatch::{DispatchClass, DispatchResultWithPostInfo},99 inherent::Vec,100 pallet_prelude::*,101 sp_runtime::traits::{AccountIdConversion, CheckedSub, Saturating, Zero},102 traits::{103 Currency, EnsureOrigin, ExistenceRequirement::KeepAlive, ReservableCurrency,104 ValidatorRegistration,105 },106 BoundedVec, PalletId,107 };108 use frame_system::pallet_prelude::*;109 use pallet_session::SessionManager;110 use sp_runtime::{Perbill, traits::Convert};111 use pallet_configuration::{112 CollatorSelectionDesiredCollatorsOverride as DesiredCollators,113 CollatorSelectionLicenseBondOverride as LicenseBond,114 CollatorSelectionKickThresholdOverride as KickThreshold, BalanceOf,115 };116 use sp_staking::SessionIndex;117118 /// A convertor from collators id. Since this pallet does not have stash/controller, this is119 /// just identity.120 pub struct IdentityCollator;121 impl<T> sp_runtime::traits::Convert<T, Option<T>> for IdentityCollator {122 fn convert(t: T) -> Option<T> {123 Some(t)124 }125 }126127 /// Configure the pallet by specifying the parameters and types on which it depends.128 #[pallet::config]129 pub trait Config: frame_system::Config + pallet_configuration::Config {130 /// Overarching event type.131 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;132133 /// Origin that can dictate updating parameters of this pallet.134 type UpdateOrigin: EnsureOrigin<Self::RuntimeOrigin>;135136 /// Account Identifier that holds the chain's treasury.137 type TreasuryAccountId: Get<Self::AccountId>;138139 /// Account Identifier from which the internal Pot is generated.140 type PotId: Get<PalletId>;141142 /// Maximum number of candidates and invulnerables that we should have. This is enforced in code.143 type MaxCollators: Get<u32>;144145 /// If kicked, how much of the collator's deposit will be slashed and sent to the slash destination.146 type SlashRatio: Get<Perbill>;147148 /// A stable ID for a validator.149 type ValidatorId: Member + Parameter;150151 /// A conversion from account ID to validator ID.152 ///153 /// Its cost must be at most one storage read.154 type ValidatorIdOf: Convert<Self::AccountId, Option<Self::ValidatorId>>;155156 /// Validate a user is registered157 type ValidatorRegistration: ValidatorRegistration<Self::ValidatorId>;158159 /// The weight information of this pallet.160 type WeightInfo: WeightInfo;161 }162163 #[pallet::pallet]164 pub struct Pallet<T>(_);165166 /// The invulnerable, fixed collators.167 #[pallet::storage]168 #[pallet::getter(fn invulnerables)]169 pub type Invulnerables<T: Config> =170 StorageValue<_, BoundedVec<T::AccountId, T::MaxCollators>, ValueQuery>;171172 /// The (community) collation license holders.173 #[pallet::storage]174 #[pallet::getter(fn license_deposit_of)]175 pub type LicenseDepositOf<T: Config> =176 StorageMap<_, Blake2_128Concat, T::AccountId, BalanceOf<T>, ValueQuery>;177178 /// The (community, limited) collation candidates.179 #[pallet::storage]180 #[pallet::getter(fn candidates)]181 pub type Candidates<T: Config> =182 StorageValue<_, BoundedVec<T::AccountId, T::MaxCollators>, ValueQuery>;183184 /// Last block authored by collator.185 #[pallet::storage]186 #[pallet::getter(fn last_authored_block)]187 pub type LastAuthoredBlock<T: Config> =188 StorageMap<_, Twox64Concat, T::AccountId, T::BlockNumber, ValueQuery>;189190 #[pallet::genesis_config]191 pub struct GenesisConfig<T: Config> {192 pub invulnerables: Vec<T::AccountId>,193 }194195 #[cfg(feature = "std")]196 impl<T: Config> Default for GenesisConfig<T> {197 fn default() -> Self {198 Self {199 invulnerables: Default::default(),200 }201 }202 }203204 #[pallet::genesis_build]205 impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {206 fn build(&self) {207 let duplicate_invulnerables = self208 .invulnerables209 .iter()210 .collect::<std::collections::BTreeSet<_>>();211 assert!(212 duplicate_invulnerables.len() == self.invulnerables.len(),213 "duplicate invulnerables in genesis."214 );215216 let bounded_invulnerables =217 BoundedVec::<_, T::MaxCollators>::try_from(self.invulnerables.clone())218 .expect("genesis invulnerables are more than T::MaxCollators");219220 <Invulnerables<T>>::put(bounded_invulnerables);221 }222 }223224 #[pallet::event]225 #[pallet::generate_deposit(pub(super) fn deposit_event)]226 pub enum Event<T: Config> {227 InvulnerableAdded {228 invulnerable: T::AccountId,229 },230 InvulnerableRemoved {231 invulnerable: T::AccountId,232 },233 LicenseObtained {234 account_id: T::AccountId,235 deposit: BalanceOf<T>,236 },237 LicenseReleased {238 account_id: T::AccountId,239 deposit_returned: BalanceOf<T>,240 },241 CandidateAdded {242 account_id: T::AccountId,243 },244 CandidateRemoved {245 account_id: T::AccountId,246 },247 }248249 // Errors inform users that something went wrong.250 #[pallet::error]251 pub enum Error<T> {252 /// Too many candidates253 TooManyCandidates,254 /// Unknown error255 Unknown,256 /// Permission issue257 Permission,258 /// User already holds license to collate259 AlreadyHoldingLicense,260 /// User does not hold a license to collate261 NoLicense,262 /// User is already a candidate263 AlreadyCandidate,264 /// User is not a candidate265 NotCandidate,266 /// Too many invulnerables267 TooManyInvulnerables,268 /// Too few invulnerables269 TooFewInvulnerables,270 /// User is already an Invulnerable271 AlreadyInvulnerable,272 /// User is not an Invulnerable273 NotInvulnerable,274 /// Account has no associated validator ID275 NoAssociatedValidatorId,276 /// Validator ID is not yet registered277 ValidatorNotRegistered,278 }279280 #[pallet::hooks]281 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}282283 #[pallet::call]284 impl<T: Config> Pallet<T> {285 /// Add a collator to the list of invulnerable (fixed) collators.286 #[pallet::call_index(0)]287 #[pallet::weight(<T as Config>::WeightInfo::add_invulnerable(T::MaxCollators::get()))]288 pub fn add_invulnerable(289 origin: OriginFor<T>,290 new: T::AccountId,291 ) -> DispatchResultWithPostInfo {292 T::UpdateOrigin::ensure_origin(origin)?;293294 // check if the new invulnerable has associated validator keys before it is added295 let validator_key = T::ValidatorIdOf::convert(new.clone())296 .ok_or(Error::<T>::NoAssociatedValidatorId)?;297 ensure!(298 T::ValidatorRegistration::is_registered(&validator_key),299 Error::<T>::ValidatorNotRegistered300 );301 if Self::invulnerables().contains(&new) {302 return Ok(().into());303 }304305 <Invulnerables<T>>::try_append(new.clone())306 .map_err(|_| Error::<T>::TooManyInvulnerables)?;307308 // try to offboard the new invulnerable if it was a collator candidate before309 let _ = Self::try_remove_candidate(&new);310311 Self::deposit_event(Event::InvulnerableAdded { invulnerable: new });312 Ok(().into())313 }314315 /// Remove a collator from the list of invulnerable (fixed) collators.316 #[pallet::call_index(1)]317 #[pallet::weight(<T as Config>::WeightInfo::remove_invulnerable(T::MaxCollators::get()))]318 pub fn remove_invulnerable(319 origin: OriginFor<T>,320 who: T::AccountId,321 ) -> DispatchResultWithPostInfo {322 T::UpdateOrigin::ensure_origin(origin)?;323324 <Invulnerables<T>>::try_mutate(|invulnerables| -> DispatchResult {325 if invulnerables.len() <= 1 {326 return Err(Error::<T>::TooFewInvulnerables.into());327 }328329 let index = invulnerables330 .into_iter()331 .position(|r| *r == who)332 .ok_or(Error::<T>::NotInvulnerable)?;333 invulnerables.remove(index);334 Ok(())335 })?;336 Self::deposit_event(Event::InvulnerableRemoved { invulnerable: who });337 Ok(().into())338 }339340 /// Purchase a license on block collation for this account.341 /// It does not make it a collator candidate, use `onboard` afterward. The account must342 /// (a) already have registered session keys and (b) be able to reserve the `LicenseBond`.343 ///344 /// This call is not available to `Invulnerable` collators.345 #[pallet::call_index(2)]346 #[pallet::weight(<T as Config>::WeightInfo::get_license(T::MaxCollators::get()))]347 pub fn get_license(origin: OriginFor<T>) -> DispatchResultWithPostInfo {348 // register_as_candidate349 let who = ensure_signed(origin)?;350351 if LicenseDepositOf::<T>::contains_key(&who) {352 return Err(Error::<T>::AlreadyHoldingLicense.into());353 }354355 let validator_key = T::ValidatorIdOf::convert(who.clone())356 .ok_or(Error::<T>::NoAssociatedValidatorId)?;357 ensure!(358 T::ValidatorRegistration::is_registered(&validator_key),359 Error::<T>::ValidatorNotRegistered360 );361362 let deposit = <LicenseBond<T>>::get();363364 T::Currency::reserve(&who, deposit)?;365 LicenseDepositOf::<T>::insert(who.clone(), deposit);366367 Self::deposit_event(Event::LicenseObtained {368 account_id: who,369 deposit,370 });371 Ok(().into()) // Some(T::WeightInfo::register_as_candidate(current_count as u32)).into())372 }373374 /// Register this account as a candidate for collators for next sessions.375 /// The account must already hold a license, and cannot offboard immediately during a session.376 ///377 /// This call is not available to `Invulnerable` collators.378 #[pallet::call_index(3)]379 #[pallet::weight(<T as Config>::WeightInfo::onboard(T::MaxCollators::get()))]380 pub fn onboard(origin: OriginFor<T>) -> DispatchResultWithPostInfo {381 // register_as_candidate382 let who = ensure_signed(origin)?;383384 // ensure the user obtained the license.385 ensure!(386 LicenseDepositOf::<T>::contains_key(&who),387 Error::<T>::NoLicense388 );389 // ensure we are below limit.390 let length = <Candidates<T>>::decode_len().unwrap_or_default()391 + <Invulnerables<T>>::decode_len().unwrap_or_default();392 ensure!(393 (length as u32) < <DesiredCollators<T>>::get(),394 Error::<T>::TooManyCandidates395 );396 ensure!(397 !Self::invulnerables().contains(&who),398 Error::<T>::AlreadyInvulnerable399 );400401 let current_count =402 <Candidates<T>>::try_mutate(|candidates| -> Result<usize, DispatchError> {403 if candidates.iter().any(|candidate| *candidate == who) {404 Err(Error::<T>::AlreadyCandidate)?405 } else {406 candidates407 .try_push(who.clone())408 .map_err(|_| Error::<T>::TooManyCandidates)?;409 // First authored block is current block plus kick threshold to handle session delay410 <LastAuthoredBlock<T>>::insert(411 who.clone(),412 frame_system::Pallet::<T>::block_number() + <KickThreshold<T>>::get(),413 );414 Ok(candidates.len())415 }416 })?;417418 Self::deposit_event(Event::CandidateAdded { account_id: who });419 Ok(Some(<T as Config>::WeightInfo::onboard(current_count as u32)).into())420 }421422 /// Deregister `origin` as a collator candidate. Note that the collator can only leave on423 /// session change. The license to `onboard` later at any other time will remain.424 #[pallet::call_index(4)]425 #[pallet::weight(<T as Config>::WeightInfo::offboard(T::MaxCollators::get()))]426 pub fn offboard(origin: OriginFor<T>) -> DispatchResultWithPostInfo {427 // leave_intent428 let who = ensure_signed(origin)?;429 let current_count = Self::try_remove_candidate(&who)?;430431 Ok(Some(<T as Config>::WeightInfo::offboard(current_count as u32)).into())432 }433434 /// Forfeit `origin`'s own license. The `LicenseBond` will be unreserved immediately.435 ///436 /// This call is not available to `Invulnerable` collators.437 #[pallet::call_index(5)]438 #[pallet::weight(<T as Config>::WeightInfo::release_license(T::MaxCollators::get()))]439 pub fn release_license(origin: OriginFor<T>) -> DispatchResultWithPostInfo {440 // leave_intent441 let who = ensure_signed(origin)?;442443 let current_count = Self::try_remove_candidate_and_release_license(&who, false, true)?;444445 Ok(Some(<T as Config>::WeightInfo::release_license(446 current_count as u32,447 ))448 .into())449 }450451 /// Force deregister `origin` as a collator candidate as a governing authority, and revoke its license.452 /// Note that the collator can only leave on session change.453 /// The `LicenseBond` will be unreserved and returned immediately.454 ///455 /// This call is, of course, not applicable to `Invulnerable` collators.456 #[pallet::call_index(6)]457 #[pallet::weight(<T as Config>::WeightInfo::force_release_license(T::MaxCollators::get()))]458 pub fn force_release_license(459 origin: OriginFor<T>,460 who: T::AccountId,461 ) -> DispatchResultWithPostInfo {462 // leave_intent463 T::UpdateOrigin::ensure_origin(origin)?;464465 let current_count = Self::try_remove_candidate_and_release_license(&who, false, true)?;466467 Ok(Some(<T as Config>::WeightInfo::force_release_license(468 current_count as u32,469 ))470 .into())471 }472 }473474 impl<T: Config> Pallet<T> {475 /// Get a unique, inaccessible account id from the `PotId`.476 pub fn account_id() -> T::AccountId {477 T::PotId::get().into_account_truncating()478 }479480 /// Removes a candidate and their license, optionally slashed and optionally ignoring,481 /// whether or not they actually are a candidate.482 fn try_remove_candidate_and_release_license(483 who: &T::AccountId,484 should_slash: bool,485 ignore_if_not_candidate: bool,486 ) -> Result<usize, DispatchError> {487 let current_count = Self::try_remove_candidate(who);488 let current_count = if ignore_if_not_candidate489 && current_count == Err(Error::<T>::NotCandidate.into())490 {491 <Candidates<T>>::decode_len().unwrap_or_default()492 } else {493 current_count?494 };495 Self::try_release_license(who, should_slash)?;496 Ok(current_count)497 }498499 /// Removes a candidate from the collator pool for the next session if they exist.500 fn try_remove_candidate(who: &T::AccountId) -> Result<usize, DispatchError> {501 let current_count =502 <Candidates<T>>::try_mutate(|candidates| -> Result<usize, DispatchError> {503 let index = candidates504 .iter()505 .position(|candidate| *candidate == *who)506 .ok_or(Error::<T>::NotCandidate)?;507 candidates.remove(index);508 <LastAuthoredBlock<T>>::remove(who.clone());509 Ok(candidates.len())510 })?;511 Self::deposit_event(Event::CandidateRemoved {512 account_id: who.clone(),513 });514 Ok(current_count)515 }516517 /// Removes a candidate if they exist and sends them back their deposit, optionally slashed.518 fn try_release_license(who: &T::AccountId, should_slash: bool) -> DispatchResult {519 let mut deposit_returned = BalanceOf::<T>::default();520 LicenseDepositOf::<T>::try_mutate_exists(who, |deposit| -> DispatchResult {521 if let Some(deposit) = deposit.take() {522 if should_slash {523 let slashed = T::SlashRatio::get() * deposit;524 let remaining = deposit - slashed;525526 let (imbalance, _) = T::Currency::slash_reserved(who, slashed);527 //T::Currency::unreserve(who, remaining);528 deposit_returned = remaining;529530 T::Currency::resolve_creating(&T::TreasuryAccountId::get(), imbalance);531 } else {532 //T::Currency::unreserve(who, deposit);533 deposit_returned = deposit;534 }535536 T::Currency::unreserve(who, deposit_returned);537 Ok(())538 } else {539 Err(Error::<T>::NoLicense.into())540 }541 })?;542 Self::deposit_event(Event::LicenseReleased {543 account_id: who.clone(),544 deposit_returned,545 });546 Ok(())547 }548549 /// Assemble the current set of candidates and invulnerables into the next collator set.550 ///551 /// This is done on the fly, as frequent as we are told to do so, as the session manager.552 pub fn assemble_collators(553 candidates: BoundedVec<T::AccountId, T::MaxCollators>,554 ) -> Vec<T::AccountId> {555 let mut collators = Self::invulnerables().to_vec();556 collators.extend(candidates);557 collators558 }559560 /// Kicks out candidates that did not produce a block in the kick threshold561 /// and **confiscates** their deposits to the treasury.562 pub fn kick_stale_candidates(563 candidates: BoundedVec<T::AccountId, T::MaxCollators>,564 ) -> BoundedVec<T::AccountId, T::MaxCollators> {565 let now = frame_system::Pallet::<T>::block_number();566 let kick_threshold = <KickThreshold<T>>::get();567 candidates568 .into_iter()569 .filter_map(|c| {570 let last_block = <LastAuthoredBlock<T>>::get(c.clone());571 let since_last = now.saturating_sub(last_block);572 if since_last < kick_threshold {573 Some(c)574 } else {575 let outcome = Self::try_remove_candidate_and_release_license(&c, true, false);576 if let Err(why) = outcome {577 log::warn!("Failed to kick collator and release license {:?}", why);578 debug_assert!(false, "failed to kick collator and release license {why:?}");579 }580 None581 }582 })583 .collect::<Vec<_>>()584 .try_into()585 .expect("filter_map operation can't result in a bounded vec larger than its original; qed")586 }587 }588589 /// Keep track of number of authored blocks per authority, uncles are counted as well since590 /// they're a valid proof of being online.591 impl<T: Config + pallet_authorship::Config>592 pallet_authorship::EventHandler<T::AccountId, T::BlockNumber> for Pallet<T>593 {594 fn note_author(author: T::AccountId) {595 let pot = Self::account_id();596 // assumes an ED will be sent to pot.597 let reward = T::Currency::free_balance(&pot)598 .checked_sub(&T::Currency::minimum_balance())599 .unwrap_or_else(Zero::zero)600 .div(2u32.into());601 // `reward` is half of pot account minus ED, this should never fail.602 let _success = T::Currency::transfer(&pot, &author, reward, KeepAlive);603 debug_assert!(_success.is_ok());604 <LastAuthoredBlock<T>>::insert(author, frame_system::Pallet::<T>::block_number());605606 frame_system::Pallet::<T>::register_extra_weight_unchecked(607 <T as Config>::WeightInfo::note_author(),608 DispatchClass::Mandatory,609 );610 }611 }612613 /// Play the role of the session manager.614 impl<T: Config> SessionManager<T::AccountId> for Pallet<T> {615 fn new_session(index: SessionIndex) -> Option<Vec<T::AccountId>> {616 log::info!(617 "assembling new collators for new session {} at #{:?}",618 index,619 <frame_system::Pallet<T>>::block_number(),620 );621622 let candidates = Self::candidates();623 let candidates_len_before = candidates.len();624 let active_candidates = Self::kick_stale_candidates(candidates);625 let removed = candidates_len_before - active_candidates.len();626 let result = Self::assemble_collators(active_candidates);627628 frame_system::Pallet::<T>::register_extra_weight_unchecked(629 <T as Config>::WeightInfo::new_session(630 candidates_len_before as u32,631 removed as u32,632 ),633 DispatchClass::Mandatory,634 );635 Some(result)636 }637 fn start_session(_: SessionIndex) {638 // we don't care.639 }640 fn end_session(_: SessionIndex) {641 // we don't care.642 }643 }644}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;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 //T::Currency::unreserve(who, remaining);535 deposit_returned = remaining;536537 T::Currency::resolve(&T::TreasuryAccountId::get(), imbalance)538 .map_err(|_| DispatchError::Other("Failed to deposit imbalance"))?;539 } else {540 //T::Currency::unreserve(who, deposit);541 deposit_returned = deposit;542 }543544 T::Currency::release(545 &T::LicenceBondIdentifier::get(),546 who,547 deposit_returned,548 Precision::Exact,549 )?;550 Ok(())551 } else {552 Err(Error::<T>::NoLicense.into())553 }554 })?;555 Self::deposit_event(Event::LicenseReleased {556 account_id: who.clone(),557 deposit_returned,558 });559 Ok(())560 }561562 /// Assemble the current set of candidates and invulnerables into the next collator set.563 ///564 /// This is done on the fly, as frequent as we are told to do so, as the session manager.565 pub fn assemble_collators(566 candidates: BoundedVec<T::AccountId, T::MaxCollators>,567 ) -> Vec<T::AccountId> {568 let mut collators = Self::invulnerables().to_vec();569 collators.extend(candidates);570 collators571 }572573 /// Kicks out candidates that did not produce a block in the kick threshold574 /// and **confiscates** their deposits to the treasury.575 pub fn kick_stale_candidates(576 candidates: BoundedVec<T::AccountId, T::MaxCollators>,577 ) -> BoundedVec<T::AccountId, T::MaxCollators> {578 let now = frame_system::Pallet::<T>::block_number();579 let kick_threshold = <KickThreshold<T>>::get();580 candidates581 .into_iter()582 .filter_map(|c| {583 let last_block = <LastAuthoredBlock<T>>::get(c.clone());584 let since_last = now.saturating_sub(last_block);585 if since_last < kick_threshold {586 Some(c)587 } else {588 let outcome = Self::try_remove_candidate_and_release_license(&c, true, false);589 if let Err(why) = outcome {590 log::warn!("Failed to kick collator and release license {:?}", why);591 debug_assert!(false, "failed to kick collator and release license {why:?}");592 }593 None594 }595 })596 .collect::<Vec<_>>()597 .try_into()598 .expect("filter_map operation can't result in a bounded vec larger than its original; qed")599 }600 }601602 /// Keep track of number of authored blocks per authority, uncles are counted as well since603 /// they're a valid proof of being online.604 impl<T: Config + pallet_authorship::Config>605 pallet_authorship::EventHandler<T::AccountId, T::BlockNumber> for Pallet<T>606 {607 fn note_author(author: T::AccountId) {608 let pot = Self::account_id();609 // assumes an ED will be sent to pot.610 let reward = T::Currency::balance(&pot)611 .checked_sub(&T::Currency::minimum_balance())612 .unwrap_or_else(Zero::zero)613 .div(2u32.into());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 <LastAuthoredBlock<T>>::insert(author, frame_system::Pallet::<T>::block_number());618619 frame_system::Pallet::<T>::register_extra_weight_unchecked(620 <T as Config>::WeightInfo::note_author(),621 DispatchClass::Mandatory,622 );623 }624 }625626 /// Play the role of the session manager.627 impl<T: Config> SessionManager<T::AccountId> for Pallet<T> {628 fn new_session(index: SessionIndex) -> Option<Vec<T::AccountId>> {629 log::info!(630 "assembling new collators for new session {} at #{:?}",631 index,632 <frame_system::Pallet<T>>::block_number(),633 );634635 let candidates = Self::candidates();636 let candidates_len_before = candidates.len();637 let active_candidates = Self::kick_stale_candidates(candidates);638 let removed = candidates_len_before - active_candidates.len();639 let result = Self::assemble_collators(active_candidates);640641 frame_system::Pallet::<T>::register_extra_weight_unchecked(642 <T as Config>::WeightInfo::new_session(643 candidates_len_before as u32,644 removed as u32,645 ),646 DispatchClass::Mandatory,647 );648 Some(result)649 }650 fn start_session(_: SessionIndex) {651 // we don't care.652 }653 fn end_session(_: SessionIndex) {654 // we don't care.655 }656 }657}pallets/collator-selection/src/tests.rsdiffbeforeafterboth--- a/pallets/collator-selection/src/tests.rs
+++ b/pallets/collator-selection/src/tests.rs
@@ -417,7 +417,10 @@
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);
+ <pallet_balances::Pallet<T> as fungible::Mutate<T::AccountId>>::set_balance(
+ &CollatorSelection::account_id(),
+ 105,
+ );
// 4 is the default author.
assert_eq!(Balances::free_balance(4), 100);
@@ -441,7 +444,10 @@
// 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);
+ <pallet_balances::Pallet<T> as fungible::Mutate<T::AccountId>>::set_balance(
+ &CollatorSelection::account_id(),
+ 5,
+ );
// 4 is the default author.
assert_eq!(Balances::free_balance(4), 100);
get_license_and_onboard(4);
pallets/common/src/benchmarking.rsdiffbeforeafterboth--- a/pallets/common/src/benchmarking.rs
+++ b/pallets/common/src/benchmarking.rs
@@ -27,12 +27,12 @@
MAX_PROPERTIES_PER_ITEM,
};
use frame_support::{
- traits::{Currency, Get},
+ traits::{Get, fungible::Balanced, Imbalance, tokens::Precision},
pallet_prelude::ConstU32,
BoundedVec,
};
use core::convert::TryInto;
-use sp_runtime::DispatchError;
+use sp_runtime::{DispatchError, traits::Zero};
const SEED: u32 = 1;
@@ -85,7 +85,12 @@
) -> Result<CollectionId, DispatchError>,
cast: impl FnOnce(CollectionHandle<T>) -> R,
) -> Result<R, DispatchError> {
- <T as Config>::Currency::deposit_creating(&owner.as_sub(), T::CollectionCreationPrice::get());
+ let imbalance = <T as Config>::Currency::deposit(
+ &owner.as_sub(),
+ T::CollectionCreationPrice::get(),
+ Precision::Exact,
+ )?;
+ debug_assert!(imbalance.peek().is_zero());
let name = create_u16_data::<MAX_COLLECTION_NAME_LENGTH>();
let description = create_u16_data::<MAX_COLLECTION_DESCRIPTION_LENGTH>();
let token_prefix = create_data::<MAX_TOKEN_PREFIX_LENGTH>();
pallets/common/src/lib.rsdiffbeforeafterboth--- a/pallets/common/src/lib.rs
+++ b/pallets/common/src/lib.rs
@@ -64,7 +64,11 @@
use frame_support::{
dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, Weight, PostDispatchInfo},
ensure,
- traits::{Imbalance, Get, Currency, WithdrawReasons, ExistenceRequirement},
+ traits::{
+ Get,
+ fungible::{Balanced, Debt, Inspect},
+ tokens::{Imbalance, Precision, Preservation},
+ },
dispatch::Pays,
transactional, fail,
};
@@ -85,7 +89,7 @@
pub use pallet::*;
use sp_core::H160;
-use sp_runtime::{ArithmeticError, DispatchError, DispatchResult};
+use sp_runtime::{ArithmeticError, DispatchError, DispatchResult, traits::Zero};
#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
@@ -424,7 +428,6 @@
use super::*;
use dispatch::CollectionDispatch;
use frame_support::{Blake2_128Concat, pallet_prelude::*, storage::Key, traits::StorageVersion};
- use frame_support::traits::Currency;
use up_data_structs::{TokenId, mapping::TokenAddressMapping};
use scale_info::TypeInfo;
use weights::WeightInfo;
@@ -440,12 +443,12 @@
type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;
/// Handler of accounts and payment.
- type Currency: Currency<Self::AccountId>;
+ type Currency: Balanced<Self::AccountId> + Inspect<Self::AccountId>;
/// Set price to create a collection.
#[pallet::constant]
type CollectionCreationPrice: Get<
- <<Self as Config>::Currency as Currency<Self::AccountId>>::Balance,
+ <<Self as Config>::Currency as Inspect<Self::AccountId>>::Balance,
>;
/// Dispatcher of operations on collections.
@@ -1112,21 +1115,17 @@
// Take a (non-refundable) deposit of collection creation
{
- let mut imbalance =
- <<<T as Config>::Currency as Currency<T::AccountId>>::PositiveImbalance>::zero();
- imbalance.subsume(
- <<T as Config>::Currency as Currency<T::AccountId>>::deposit_creating(
- &T::TreasuryAccountId::get(),
- T::CollectionCreationPrice::get(),
- ),
- );
- <T as Config>::Currency::settle(
- payer.as_sub(),
- imbalance,
- WithdrawReasons::TRANSFER,
- ExistenceRequirement::KeepAlive,
- )
- .map_err(|_| Error::<T>::NotSufficientFounds)?;
+ let mut imbalance = <Debt<T::AccountId, <T as Config>::Currency>>::zero();
+ imbalance.subsume(<T as Config>::Currency::deposit(
+ &T::TreasuryAccountId::get(),
+ T::CollectionCreationPrice::get(),
+ Precision::Exact,
+ )?);
+ let credit =
+ <T as Config>::Currency::settle(payer.as_sub(), imbalance, Preservation::Preserve)
+ .map_err(|_| Error::<T>::NotSufficientFounds)?;
+
+ debug_assert!(credit.peek().is_zero())
}
<CreatedCollectionCount<T>>::put(created_count);
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::Currency};
+use frame_support::{assert_ok, traits::fungible::Inspect};
fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
let events = frame_system::Pallet::<T>::events();
@@ -68,7 +68,7 @@
}
set_collator_selection_license_bond {
- let bond_cost: Option<BalanceOf<T>> = Some(T::Currency::minimum_balance() * 10u32.into());
+ let bond_cost: Option<BalanceOf<T>> = Some(T::Balances::minimum_balance() * 10u32.into());
}: {
assert_ok!(
<Pallet<T>>::set_collator_selection_license_bond(RawOrigin::Root.into(), bond_cost.clone())
pallets/configuration/src/lib.rsdiffbeforeafterboth--- a/pallets/configuration/src/lib.rs
+++ b/pallets/configuration/src/lib.rs
@@ -42,7 +42,7 @@
mod pallet {
use super::*;
use frame_support::{
- traits::{Get, ReservableCurrency, Currency},
+ traits::{fungible, Get, ReservableCurrency, Currency},
pallet_prelude::{StorageValue, ValueQuery, DispatchResult, IsType},
log,
};
@@ -50,15 +50,19 @@
pub use crate::weights::WeightInfo;
pub type BalanceOf<T> =
- <<T as Config>::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance;
+ <<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>;
- /// The currency mechanism.
- type Currency: ReservableCurrency<Self::AccountId>;
+ 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>;
#[pallet::constant]
type DefaultWeightToFeeCoefficient: Get<u64>;