#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(doc)]
use aquamarine::aquamarine;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarks;
#[cfg(test)]
#[cfg(not(feature = "runtime-benchmarks"))]
mod mock;
mod set;
#[cfg(test)]
#[cfg(not(feature = "runtime-benchmarks"))]
mod tests;
use crate::set::OrderedSet;
use codec::{Decode, Encode};
use frame_support::{
pallet,
pallet_prelude::*,
traits::{
tokens::currency::MultiTokenCurrency, EstimateNextSessionRotation, ExistenceRequirement,
Get,
},
transactional,
};
use frame_system::{pallet_prelude::*, RawOrigin};
pub use mangata_support::traits::{
ComputeIssuance, GetIssuance, PoolCreateApi, ProofOfStakeRewardsApi,
StakingReservesProviderTrait, Valuate, XykFunctionsTrait,
};
pub use mangata_types::multipurpose_liquidity::BondKind;
use orml_tokens::{MultiTokenCurrencyExtended, MultiTokenReservableCurrency};
use pallet_collective_mangata::GetMembers;
use scale_info::TypeInfo;
use sp_arithmetic::per_things::Rounding;
use sp_runtime::{
helpers_128bit::multiply_by_rational_with_rounding,
traits::{
Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, One, SaturatedConversion,
Saturating, Zero,
},
Perbill, Permill, RuntimeDebug,
};
use sp_staking::SessionIndex;
use sp_std::{
cmp::Ordering,
collections::{btree_map::BTreeMap, btree_set::BTreeSet},
convert::TryInto,
prelude::*,
};
pub use pallet::*;
pub mod weights;
pub use weights::WeightInfo;
trait FromInfiniteZeros {
type Output;
fn from_zeros() -> Self::Output;
}
impl<D: Decode> FromInfiniteZeros for D {
type Output = D;
fn from_zeros() -> Self::Output {
D::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()).unwrap()
}
}
#[derive(Eq, PartialEq, Encode, Decode, TypeInfo, Debug, Clone)]
pub enum MetadataUpdateAction {
ExtendApprovedCollators,
RemoveApprovedCollators,
}
#[derive(Eq, PartialEq, Debug, Clone)]
pub enum RewardKind<AccountId> {
Collator,
Delegator(AccountId),
}
#[derive(Eq, PartialEq, Encode, Decode, TypeInfo, Debug, Clone)]
pub enum PayoutRounds {
All,
Partial(Vec<RoundIndex>),
}
#[pallet]
pub mod pallet {
pub use super::*;
#[pallet::pallet]
#[pallet::without_storage_info]
pub struct Pallet<T>(PhantomData<T>);
#[derive(Eq, PartialEq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
pub enum PairedOrLiquidityToken<CurrencyId> {
Paired(CurrencyId),
Liquidity(CurrencyId),
}
#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct Bond<AccountId, Balance, CurrencyId> {
pub owner: AccountId,
pub amount: Balance,
pub liquidity_token: CurrencyId,
}
impl<A: Decode, B: Default, C: Default> Default for Bond<A, B, C> {
fn default() -> Bond<A, B, C> {
Bond {
owner: A::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes())
.expect("infinite length input; no invalid inputs for type; qed"),
amount: B::default(),
liquidity_token: C::default(),
}
}
}
impl<A, B: Default, C: Default> Bond<A, B, C> {
pub fn from_owner(owner: A) -> Self {
Bond { owner, amount: B::default(), liquidity_token: C::default() }
}
}
impl<AccountId: Ord, Balance, CurrencyId> Eq for Bond<AccountId, Balance, CurrencyId> {}
impl<AccountId: Ord, Balance, CurrencyId> Ord for Bond<AccountId, Balance, CurrencyId> {
fn cmp(&self, other: &Self) -> Ordering {
self.owner.cmp(&other.owner)
}
}
impl<AccountId: Ord, Balance, CurrencyId> PartialOrd for Bond<AccountId, Balance, CurrencyId> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<AccountId: Ord, Balance, CurrencyId> PartialEq for Bond<AccountId, Balance, CurrencyId> {
fn eq(&self, other: &Self) -> bool {
self.owner == other.owner
}
}
#[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub enum CollatorStatus {
Active,
Idle,
Leaving(RoundIndex),
}
impl Default for CollatorStatus {
fn default() -> CollatorStatus {
CollatorStatus::Active
}
}
#[derive(Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct CollatorSnapshot<AccountId, Balance, CurrencyId> {
pub bond: Balance,
pub delegations: Vec<Bond<AccountId, Balance, CurrencyId>>,
pub total: Balance,
pub liquidity_token: CurrencyId,
}
impl<AccountId, Balance: Default, CurrencyId: Default> Default
for CollatorSnapshot<AccountId, Balance, CurrencyId>
{
fn default() -> CollatorSnapshot<AccountId, Balance, CurrencyId> {
Self {
delegations: Default::default(),
bond: Default::default(),
total: Default::default(),
liquidity_token: Default::default(),
}
}
}
#[derive(PartialEq, Clone, Copy, Encode, Decode, RuntimeDebug, TypeInfo)]
pub enum CandidateBondChange {
Increase,
Decrease,
}
#[derive(PartialEq, Clone, Copy, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct CandidateBondRequest<Balance> {
pub amount: Balance,
pub change: CandidateBondChange,
pub when_executable: RoundIndex,
}
#[derive(Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct CollatorCandidate<AccountId, Balance, CurrencyId> {
pub id: AccountId,
pub bond: Balance,
pub liquidity_token: CurrencyId,
pub delegators: OrderedSet<AccountId>,
pub top_delegations: Vec<Bond<AccountId, Balance, CurrencyId>>,
pub bottom_delegations: Vec<Bond<AccountId, Balance, CurrencyId>>,
pub total_counted: Balance,
pub total_backing: Balance,
pub request: Option<CandidateBondRequest<Balance>>,
pub state: CollatorStatus,
}
#[derive(Clone, Copy, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub enum DelegatorAdded<Balance> {
AddedToTop { new_total: Balance },
AddedToBottom,
}
impl<A, Balance, CurrencyId> CollatorCandidate<A, Balance, CurrencyId>
where
A: Ord + Clone + sp_std::fmt::Debug,
Balance: Default + PartialOrd + CheckedAdd + CheckedSub + Saturating + Ord + Copy,
CurrencyId: Copy,
{
pub fn new(id: A, bond: Balance, liquidity_token: CurrencyId) -> Self {
CollatorCandidate {
id,
bond,
liquidity_token,
delegators: OrderedSet::new(),
top_delegations: Vec::new(),
bottom_delegations: Vec::new(),
total_counted: bond,
total_backing: bond,
request: None,
state: CollatorStatus::default(), }
}
pub fn is_active(&self) -> bool {
self.state == CollatorStatus::Active
}
pub fn is_leaving(&self) -> bool {
matches!(self.state, CollatorStatus::Leaving(_))
}
pub fn can_leave<T: Config>(&self) -> DispatchResult {
if let CollatorStatus::Leaving(when) = self.state {
ensure!(<Round<T>>::get().current >= when, Error::<T>::CandidateCannotLeaveYet);
Ok(())
} else {
Err(Error::<T>::CandidateNotLeaving.into())
}
}
pub fn schedule_bond_more<T: Config>(
&mut self,
more: Balance,
use_balance_from: Option<BondKind>,
) -> Result<RoundIndex, DispatchError>
where
T::AccountId: From<A>,
BalanceOf<T>: From<Balance>,
CurrencyIdOf<T>: From<CurrencyId>,
{
ensure!(self.request.is_none(), Error::<T>::PendingCandidateRequestAlreadyExists);
let candidate_id: T::AccountId = self.id.clone().into();
ensure!(
<T as pallet::Config>::StakingReservesProvider::can_bond(
self.liquidity_token.into(),
&candidate_id,
more.into(),
use_balance_from
),
Error::<T>::InsufficientBalance
);
let when_executable =
<Round<T>>::get().current.saturating_add(T::CandidateBondDelay::get());
self.request = Some(CandidateBondRequest {
change: CandidateBondChange::Increase,
amount: more,
when_executable,
});
Ok(when_executable)
}
pub fn schedule_bond_less<T: Config>(
&mut self,
less: Balance,
) -> Result<RoundIndex, DispatchError>
where
BalanceOf<T>: From<Balance>,
CurrencyIdOf<T>: From<CurrencyId>,
{
ensure!(self.request.is_none(), Error::<T>::PendingCandidateRequestAlreadyExists);
ensure!(self.bond > less, Error::<T>::CandidateBondBelowMin);
let bond_valution_after = Pallet::<T>::valuate_bond(
self.liquidity_token.into(),
self.bond.checked_sub(&less).unwrap_or_default().into(),
);
ensure!(
bond_valution_after >= T::MinCandidateStk::get(),
Error::<T>::CandidateBondBelowMin
);
let when_executable =
<Round<T>>::get().current.saturating_add(T::CandidateBondDelay::get());
self.request = Some(CandidateBondRequest {
change: CandidateBondChange::Decrease,
amount: less,
when_executable,
});
Ok(when_executable)
}
pub fn execute_pending_request<T: Config>(
&mut self,
use_balance_from: Option<BondKind>,
) -> Result<Event<T>, DispatchError>
where
T::AccountId: From<A>,
BalanceOf<T>: From<Balance>,
CurrencyIdOf<T>: From<CurrencyId>,
{
let request = self.request.ok_or(Error::<T>::PendingCandidateRequestsDNE)?;
ensure!(
request.when_executable <= <Round<T>>::get().current,
Error::<T>::PendingCandidateRequestNotDueYet
);
let caller: T::AccountId = self.id.clone().into();
let event = match request.change {
CandidateBondChange::Increase => {
self.bond =
self.bond.checked_add(&request.amount).ok_or(Error::<T>::MathError)?;
self.total_counted = self
.total_counted
.checked_add(&request.amount)
.ok_or(Error::<T>::MathError)?;
self.total_backing = self
.total_backing
.checked_add(&request.amount)
.ok_or(Error::<T>::MathError)?;
<T as pallet::Config>::StakingReservesProvider::bond(
self.liquidity_token.into(),
&caller,
request.amount.into(),
use_balance_from,
)?;
let currency: CurrencyIdOf<T> = self.liquidity_token.into();
let new_total = <Total<T>>::get(currency).saturating_add(request.amount.into());
<Total<T>>::insert(currency, new_total);
Event::CandidateBondedMore(
self.id.clone().into(),
request.amount.into(),
self.bond.into(),
)
},
CandidateBondChange::Decrease => {
self.bond =
self.bond.checked_sub(&request.amount).ok_or(Error::<T>::MathError)?;
self.total_counted = self
.total_counted
.checked_sub(&request.amount)
.ok_or(Error::<T>::MathError)?;
self.total_backing = self
.total_backing
.checked_sub(&request.amount)
.ok_or(Error::<T>::MathError)?;
let debug_amount = <T as pallet::Config>::StakingReservesProvider::unbond(
self.liquidity_token.into(),
&caller,
request.amount.into(),
);
if !debug_amount.is_zero() {
log::warn!("Unbond in staking returned non-zero value {:?}", debug_amount);
}
let currency: CurrencyIdOf<T> = self.liquidity_token.into();
let new_total_staked =
<Total<T>>::get(currency).saturating_sub(request.amount.into());
<Total<T>>::insert(currency, new_total_staked);
Event::CandidateBondedLess(
self.id.clone().into(),
request.amount.into(),
self.bond.into(),
)
},
};
self.request = None;
if self.is_active() {
Pallet::<T>::update_active(
self.id.clone().into(),
self.total_counted.into(),
self.liquidity_token.into(),
);
}
Ok(event)
}
pub fn cancel_pending_request<T: Config>(&mut self) -> Result<Event<T>, DispatchError>
where
T::AccountId: From<A>,
CandidateBondRequest<BalanceOf<T>>: From<CandidateBondRequest<Balance>>,
{
let request = self.request.ok_or(Error::<T>::PendingCandidateRequestsDNE)?;
let event = Event::CancelledCandidateBondChange(self.id.clone().into(), request.into());
self.request = None;
Ok(event)
}
pub fn add_top_delegation(&mut self, delegation: Bond<A, Balance, CurrencyId>) {
match self.top_delegations.binary_search_by(|x| delegation.amount.cmp(&x.amount)) {
Ok(i) => self.top_delegations.insert(i, delegation),
Err(i) => self.top_delegations.insert(i, delegation),
}
}
pub fn add_bottom_delegation(&mut self, delegation: Bond<A, Balance, CurrencyId>) {
match self.bottom_delegations.binary_search_by(|x| x.amount.cmp(&delegation.amount)) {
Ok(i) => self.bottom_delegations.insert(i, delegation),
Err(i) => self.bottom_delegations.insert(i, delegation),
}
}
pub fn sort_top_delegations(&mut self) {
self.top_delegations.sort_unstable_by(|a, b| b.amount.cmp(&a.amount));
}
pub fn sort_bottom_delegations(&mut self) {
self.bottom_delegations.sort_unstable_by(|a, b| a.amount.cmp(&b.amount));
}
pub fn add_delegation<T: Config>(
&mut self,
acc: A,
amount: Balance,
) -> Result<DelegatorAdded<BalanceOf<T>>, DispatchError>
where
BalanceOf<T>: From<Balance>,
{
ensure!(self.delegators.insert(acc.clone()), Error::<T>::DelegatorExists);
self.total_backing =
self.total_backing.checked_add(&amount).ok_or(Error::<T>::MathError)?;
if (self.top_delegations.len() as u32) < T::MaxDelegatorsPerCandidate::get() {
self.add_top_delegation(Bond {
owner: acc,
amount,
liquidity_token: self.liquidity_token,
});
self.total_counted =
self.total_counted.checked_add(&amount).ok_or(Error::<T>::MathError)?;
Ok(DelegatorAdded::AddedToTop { new_total: self.total_counted.into() })
} else {
let last_delegation_in_top = self
.top_delegations
.pop()
.expect("self.top_delegations.len() >= T::Max exists >= 1 element in top");
if amount > last_delegation_in_top.amount {
self.total_counted = self
.total_counted
.checked_add(
&amount
.checked_sub(&last_delegation_in_top.amount.into())
.ok_or(Error::<T>::MathError)?,
)
.ok_or(Error::<T>::MathError)?;
self.add_top_delegation(Bond {
owner: acc,
amount,
liquidity_token: self.liquidity_token,
});
self.add_bottom_delegation(last_delegation_in_top);
Ok(DelegatorAdded::AddedToTop { new_total: self.total_counted.into() })
} else {
self.top_delegations.push(last_delegation_in_top);
self.add_bottom_delegation(Bond {
owner: acc,
amount,
liquidity_token: self.liquidity_token,
});
Ok(DelegatorAdded::AddedToBottom)
}
}
}
pub fn rm_delegator<T: Config>(
&mut self,
delegator: A,
) -> Result<(bool, Balance), DispatchError> {
ensure!(self.delegators.remove(&delegator), Error::<T>::DelegatorDNEInDelegatorSet);
let mut delegation_amt: Option<Balance> = None;
self.top_delegations = self
.top_delegations
.clone()
.into_iter()
.filter_map(|d| {
if d.owner != delegator {
Some(d)
} else {
delegation_amt = Some(d.amount);
None
}
})
.collect();
if let Some(amount) = delegation_amt {
if let Some(last) = self.bottom_delegations.pop() {
self.total_counted = self
.total_counted
.checked_sub(
&amount.checked_sub(&last.amount).ok_or(Error::<T>::MathError)?,
)
.ok_or(Error::<T>::MathError)?;
self.add_top_delegation(last);
} else {
self.total_counted =
self.total_counted.checked_sub(&amount).ok_or(Error::<T>::MathError)?;
}
self.total_backing =
self.total_backing.checked_sub(&amount).ok_or(Error::<T>::MathError)?;
return Ok((true, amount))
}
self.bottom_delegations = self
.bottom_delegations
.clone()
.into_iter()
.filter_map(|d| {
if d.owner != delegator {
Some(d)
} else {
delegation_amt = Some(d.amount);
None
}
})
.collect();
let amount = delegation_amt.ok_or(Error::<T>::DelegatorDNEinTopNorBottom)?;
self.total_backing =
self.total_backing.checked_sub(&amount).ok_or(Error::<T>::MathError)?;
Ok((false, amount))
}
fn increase_delegation<T: Config>(
&mut self,
delegator: A,
more: Balance,
) -> Result<bool, DispatchError> {
let mut in_top = false;
for x in &mut self.top_delegations {
if x.owner == delegator {
x.amount = x.amount.checked_add(&more).ok_or(Error::<T>::MathError)?;
self.total_counted =
self.total_counted.checked_add(&more).ok_or(Error::<T>::MathError)?;
self.total_backing =
self.total_backing.checked_add(&more).ok_or(Error::<T>::MathError)?;
in_top = true;
break
}
}
if in_top {
self.sort_top_delegations();
return Ok(true)
}
let lowest_top = self
.top_delegations
.pop()
.expect("any bottom delegations => must exist max top delegations");
let mut move_2_top = false;
for x in &mut self.bottom_delegations {
if x.owner == delegator {
x.amount = x.amount.checked_add(&more).ok_or(Error::<T>::MathError)?;
self.total_backing =
self.total_backing.checked_add(&more).ok_or(Error::<T>::MathError)?;
move_2_top = x.amount > lowest_top.amount;
break
}
}
if move_2_top {
self.sort_bottom_delegations();
let highest_bottom = self.bottom_delegations.pop().expect("updated => exists");
self.total_counted = self
.total_counted
.checked_add(
&highest_bottom
.amount
.checked_sub(&lowest_top.amount)
.ok_or(Error::<T>::MathError)?,
)
.ok_or(Error::<T>::MathError)?;
self.add_top_delegation(highest_bottom);
self.add_bottom_delegation(lowest_top);
Ok(true)
} else {
self.top_delegations.push(lowest_top);
self.sort_bottom_delegations();
Ok(false)
}
}
pub fn decrease_delegation<T: Config>(
&mut self,
delegator: A,
less: Balance,
) -> Result<bool, DispatchError> {
let mut in_top = false;
let mut new_lowest_top: Option<Bond<A, Balance, CurrencyId>> = None;
for x in &mut self.top_delegations {
if x.owner == delegator {
x.amount = x.amount.checked_sub(&less).ok_or(Error::<T>::MathError)?;
if let Some(highest_bottom) = self.bottom_delegations.pop() {
if highest_bottom.amount > x.amount {
new_lowest_top = Some(highest_bottom);
} else {
self.bottom_delegations.push(highest_bottom);
}
}
in_top = true;
break
}
}
if in_top {
self.sort_top_delegations();
if let Some(highest_bottom) = new_lowest_top {
let lowest_top = self
.top_delegations
.pop()
.expect("must have >1 item to update, assign in_top = true");
self.total_counted = self
.total_counted
.checked_sub(
&lowest_top.amount.checked_add(&less).ok_or(Error::<T>::MathError)?,
)
.ok_or(Error::<T>::MathError)?;
self.total_counted = self
.total_counted
.checked_add(&highest_bottom.amount)
.ok_or(Error::<T>::MathError)?;
self.total_backing =
self.total_backing.checked_sub(&less).ok_or(Error::<T>::MathError)?;
self.add_top_delegation(highest_bottom);
self.add_bottom_delegation(lowest_top);
return Ok(false)
} else {
self.total_counted =
self.total_counted.checked_sub(&less).ok_or(Error::<T>::MathError)?;
self.total_backing =
self.total_backing.checked_sub(&less).ok_or(Error::<T>::MathError)?;
return Ok(true)
}
}
for x in &mut self.bottom_delegations {
if x.owner == delegator {
x.amount = x.amount.checked_sub(&less).ok_or(Error::<T>::MathError)?;
self.total_backing =
self.total_backing.checked_sub(&less).ok_or(Error::<T>::MathError)?;
break
}
}
self.sort_bottom_delegations();
Ok(false)
}
pub fn go_offline(&mut self) {
self.state = CollatorStatus::Idle;
}
pub fn go_online(&mut self) {
self.state = CollatorStatus::Active;
}
pub fn leave<T: Config>(&mut self) -> Result<(RoundIndex, RoundIndex), DispatchError> {
ensure!(!self.is_leaving(), Error::<T>::CandidateAlreadyLeaving);
let now = <Round<T>>::get().current;
let when = now.saturating_add(T::LeaveCandidatesDelay::get());
self.state = CollatorStatus::Leaving(when);
Ok((now, when))
}
}
impl<A: Clone, Balance, CurrencyId> From<CollatorCandidate<A, Balance, CurrencyId>>
for CollatorSnapshot<A, Balance, CurrencyId>
{
fn from(
other: CollatorCandidate<A, Balance, CurrencyId>,
) -> CollatorSnapshot<A, Balance, CurrencyId> {
CollatorSnapshot {
bond: other.bond,
delegations: other.top_delegations,
total: other.total_counted,
liquidity_token: other.liquidity_token,
}
}
}
#[derive(Clone, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub enum DelegatorStatus {
Active,
Leaving(RoundIndex),
}
impl Default for DelegatorStatus {
fn default() -> DelegatorStatus {
DelegatorStatus::Active
}
}
#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct Delegator<AccountId, Balance, CurrencyId> {
pub id: AccountId,
pub delegations: OrderedSet<Bond<AccountId, Balance, CurrencyId>>,
pub requests: PendingDelegationRequests<AccountId, Balance>,
pub status: DelegatorStatus,
}
impl<AccountId: Decode + Ord, Balance: Default, CurrencyId: Default> Default
for Delegator<AccountId, Balance, CurrencyId>
{
fn default() -> Self {
Self {
id: AccountId::from_zeros(),
delegations: Default::default(),
requests: Default::default(),
status: Default::default(),
}
}
}
impl<A, Balance, CurrencyId> Delegator<A, Balance, CurrencyId>
where
A: Ord + Clone,
Balance: Ord + Copy + Saturating + CheckedAdd + CheckedSub,
CurrencyId: Copy,
{
pub fn new(id: A, collator: A, amount: Balance, liquidity_token: CurrencyId) -> Self {
Delegator {
id,
delegations: OrderedSet::from(vec![Bond {
owner: collator,
amount,
liquidity_token,
}]),
requests: PendingDelegationRequests::new(),
status: DelegatorStatus::Active,
}
}
pub fn requests(&self) -> BTreeMap<A, DelegationRequest<A, Balance>> {
self.requests.requests.clone()
}
pub fn is_active(&self) -> bool {
matches!(self.status, DelegatorStatus::Active)
}
pub fn is_leaving(&self) -> bool {
matches!(self.status, DelegatorStatus::Leaving(_))
}
pub fn can_execute_leave<T: Config>(&self, delegation_weight_hint: u32) -> DispatchResult {
ensure!(
delegation_weight_hint >= (self.delegations.0.len() as u32),
Error::<T>::TooLowDelegationCountToLeaveDelegators
);
if let DelegatorStatus::Leaving(when) = self.status {
ensure!(<Round<T>>::get().current >= when, Error::<T>::DelegatorCannotLeaveYet);
Ok(())
} else {
Err(Error::<T>::DelegatorNotLeaving.into())
}
}
pub(crate) fn set_leaving(&mut self, when: RoundIndex) {
self.status = DelegatorStatus::Leaving(when);
}
pub fn schedule_leave<T: Config>(&mut self) -> (RoundIndex, RoundIndex) {
let now = <Round<T>>::get().current;
let when = now.saturating_add(T::LeaveDelegatorsDelay::get());
self.set_leaving(when);
(now, when)
}
pub fn cancel_leave(&mut self) {
self.status = DelegatorStatus::Active
}
pub fn add_delegation(&mut self, bond: Bond<A, Balance, CurrencyId>) -> bool {
if self.delegations.insert(bond) {
true
} else {
false
}
}
pub fn rm_delegation(&mut self, collator: A) -> Option<usize> {
let mut amt: Option<Balance> = None;
let delegations = self
.delegations
.0
.iter()
.filter_map(|x| {
if x.owner == collator {
amt = Some(x.amount);
None
} else {
Some(x.clone())
}
})
.collect();
if let Some(_) = amt {
self.delegations = OrderedSet::from(delegations);
Some(self.delegations.0.len())
} else {
None
}
}
pub fn schedule_increase_delegation<T: Config>(
&mut self,
collator: A,
more: Balance,
use_balance_from: Option<BondKind>,
) -> Result<RoundIndex, DispatchError>
where
T::AccountId: From<A>,
BalanceOf<T>: From<Balance>,
CurrencyIdOf<T>: From<CurrencyId>,
{
let Bond { liquidity_token, .. } = self
.delegations
.0
.iter()
.find(|b| b.owner == collator)
.ok_or(Error::<T>::DelegationDNE)?;
let delegator_id: T::AccountId = self.id.clone().into();
ensure!(
<T as pallet::Config>::StakingReservesProvider::can_bond(
(*liquidity_token).into(),
&delegator_id,
more.into(),
use_balance_from
),
Error::<T>::InsufficientBalance
);
let when = <Round<T>>::get().current.saturating_add(T::DelegationBondDelay::get());
self.requests.bond_more::<T>(collator, more, when)?;
Ok(when)
}
pub fn schedule_decrease_delegation<T: Config>(
&mut self,
collator: A,
less: Balance,
) -> Result<RoundIndex, DispatchError>
where
BalanceOf<T>: Into<Balance>,
{
let Bond { amount, .. } = self
.delegations
.0
.iter()
.find(|b| b.owner == collator)
.ok_or(Error::<T>::DelegationDNE)?;
ensure!(
*amount >= T::MinDelegation::get().into().saturating_add(less),
Error::<T>::DelegationBelowMin
);
let when = <Round<T>>::get().current.saturating_add(T::DelegationBondDelay::get());
self.requests.bond_less::<T>(collator, less, when)?;
Ok(when)
}
pub fn schedule_revoke<T: Config>(
&mut self,
collator: A,
) -> Result<(RoundIndex, RoundIndex), DispatchError> {
let Bond { amount, .. } = self
.delegations
.0
.iter()
.find(|b| b.owner == collator)
.ok_or(Error::<T>::DelegationDNE)?;
let now = <Round<T>>::get().current;
let when = now.saturating_add(T::RevokeDelegationDelay::get());
self.requests.revoke::<T>(collator, *amount, when)?;
Ok((now, when))
}
pub fn execute_pending_request<T: Config>(
&mut self,
candidate: A,
use_balance_from: Option<BondKind>,
) -> DispatchResult
where
T::AccountId: From<A>,
BalanceOf<T>: From<Balance> + Into<Balance>,
CurrencyIdOf<T>: From<CurrencyId>,
Delegator<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>:
From<Delegator<A, Balance, CurrencyId>>,
{
let now = <Round<T>>::get().current;
let DelegationRequest { amount, action, when_executable, .. } = self
.requests
.requests
.remove(&candidate)
.ok_or(Error::<T>::PendingDelegationRequestDNE)?;
ensure!(when_executable <= now, Error::<T>::PendingDelegationRequestNotDueYet);
let (balance_amt, candidate_id, delegator_id): (Balance, T::AccountId, T::AccountId) =
(amount.into(), candidate.clone().into(), self.id.clone().into());
match action {
DelegationChange::Revoke => {
let leaving = if self.delegations.0.len() == 1usize { true } else { false };
self.rm_delegation(candidate.clone());
Pallet::<T>::delegator_leaves_collator(
delegator_id.clone(),
candidate_id.clone(),
)?;
Pallet::<T>::deposit_event(Event::DelegationRevoked(
delegator_id.clone(),
candidate_id,
balance_amt.into(),
));
if leaving {
<DelegatorState<T>>::remove(&delegator_id);
Pallet::<T>::deposit_event(Event::DelegatorLeft(
delegator_id,
balance_amt.into(),
));
} else {
let nom_st: Delegator<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>> =
self.clone().into();
<DelegatorState<T>>::insert(&delegator_id, nom_st);
}
Ok(())
},
DelegationChange::Increase => {
for x in &mut self.delegations.0 {
if x.owner == candidate {
x.amount =
x.amount.checked_add(&amount).ok_or(Error::<T>::MathError)?;
let mut collator_state = <CandidateState<T>>::get(&candidate_id)
.ok_or(Error::<T>::CandidateDNE)?;
<T as pallet::Config>::StakingReservesProvider::bond(
x.liquidity_token.into(),
&self.id.clone().into(),
balance_amt.into(),
use_balance_from,
)?;
let before = collator_state.total_counted;
let in_top = collator_state.increase_delegation::<T>(
self.id.clone().into(),
balance_amt.into(),
)?;
let after = collator_state.total_counted;
if collator_state.is_active() && (before != after) {
Pallet::<T>::update_active(
candidate_id.clone(),
after,
collator_state.liquidity_token,
);
}
let new_total_staked = <Total<T>>::get(collator_state.liquidity_token)
.saturating_add(balance_amt.into());
<Total<T>>::insert(collator_state.liquidity_token, new_total_staked);
<CandidateState<T>>::insert(&candidate_id, collator_state);
let nom_st: Delegator<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>> =
self.clone().into();
<DelegatorState<T>>::insert(&delegator_id, nom_st);
Pallet::<T>::deposit_event(Event::DelegationIncreased(
delegator_id,
candidate_id,
balance_amt.into(),
in_top,
));
return Ok(())
}
}
Err(Error::<T>::DelegationDNE.into())
},
DelegationChange::Decrease => {
for x in &mut self.delegations.0 {
if x.owner == candidate {
if x.amount > amount.saturating_add(T::MinDelegation::get().into()) {
x.amount =
x.amount.checked_sub(&amount).ok_or(Error::<T>::MathError)?;
let mut collator = <CandidateState<T>>::get(&candidate_id)
.ok_or(Error::<T>::CandidateDNE)?;
let debug_amount =
<T as pallet::Config>::StakingReservesProvider::unbond(
x.liquidity_token.into(),
&delegator_id,
balance_amt.into(),
);
if !debug_amount.is_zero() {
log::warn!(
"Unbond in staking returned non-zero value {:?}",
debug_amount
);
}
let before = collator.total_counted;
let in_top = collator.decrease_delegation::<T>(
delegator_id.clone(),
balance_amt.into(),
)?;
let after = collator.total_counted;
if collator.is_active() && (before != after) {
Pallet::<T>::update_active(
candidate_id.clone(),
after,
collator.liquidity_token,
);
}
let new_total_staked = <Total<T>>::get(collator.liquidity_token)
.saturating_sub(balance_amt.into());
<Total<T>>::insert(collator.liquidity_token, new_total_staked);
<CandidateState<T>>::insert(&candidate_id, collator);
let nom_st: Delegator<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>> =
self.clone().into();
<DelegatorState<T>>::insert(&delegator_id, nom_st);
Pallet::<T>::deposit_event(Event::DelegationDecreased(
delegator_id,
candidate_id,
balance_amt.into(),
in_top,
));
return Ok(())
} else {
return Err(Error::<T>::DelegationBelowMin.into())
}
}
}
Err(Error::<T>::DelegationDNE.into())
},
}
}
pub fn cancel_pending_request<T: Config>(
&mut self,
candidate: A,
) -> Result<DelegationRequest<A, Balance>, DispatchError> {
let order = self
.requests
.requests
.remove(&candidate)
.ok_or(Error::<T>::PendingDelegationRequestDNE)?;
Ok(order)
}
}
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub enum DelegationChange {
Revoke,
Increase,
Decrease,
}
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct DelegationRequest<AccountId, Balance> {
pub collator: AccountId,
pub amount: Balance,
pub when_executable: RoundIndex,
pub action: DelegationChange,
}
#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct PendingDelegationRequests<AccountId, Balance> {
pub requests: BTreeMap<AccountId, DelegationRequest<AccountId, Balance>>,
}
impl<A: Ord, Balance> Default for PendingDelegationRequests<A, Balance> {
fn default() -> PendingDelegationRequests<A, Balance> {
PendingDelegationRequests { requests: BTreeMap::new() }
}
}
impl<A: Ord + Clone, Balance> PendingDelegationRequests<A, Balance> {
pub fn new() -> PendingDelegationRequests<A, Balance> {
PendingDelegationRequests::default()
}
pub fn bond_more<T: Config>(
&mut self,
collator: A,
amount: Balance,
when_executable: RoundIndex,
) -> DispatchResult {
ensure!(
self.requests.get(&collator).is_none(),
Error::<T>::PendingDelegationRequestAlreadyExists
);
self.requests.insert(
collator.clone(),
DelegationRequest {
collator,
amount,
when_executable,
action: DelegationChange::Increase,
},
);
Ok(())
}
pub fn bond_less<T: Config>(
&mut self,
collator: A,
amount: Balance,
when_executable: RoundIndex,
) -> DispatchResult {
ensure!(
self.requests.get(&collator).is_none(),
Error::<T>::PendingDelegationRequestAlreadyExists
);
self.requests.insert(
collator.clone(),
DelegationRequest {
collator,
amount,
when_executable,
action: DelegationChange::Decrease,
},
);
Ok(())
}
pub fn revoke<T: Config>(
&mut self,
collator: A,
amount: Balance,
when_executable: RoundIndex,
) -> DispatchResult {
ensure!(
self.requests.get(&collator).is_none(),
Error::<T>::PendingDelegationRequestAlreadyExists
);
self.requests.insert(
collator.clone(),
DelegationRequest {
collator,
amount,
when_executable,
action: DelegationChange::Revoke,
},
);
Ok(())
}
}
#[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct RoundInfo<BlockNumber> {
pub current: RoundIndex,
pub first: BlockNumber,
pub length: u32,
}
impl<
B: Copy
+ sp_std::ops::Add<Output = B>
+ sp_std::ops::Sub<Output = B>
+ From<u32>
+ PartialOrd
+ One
+ Zero
+ Saturating
+ CheckedMul,
> RoundInfo<B>
{
pub fn new(current: RoundIndex, first: B, length: u32) -> RoundInfo<B> {
RoundInfo { current, first, length }
}
pub fn should_update(&self, now: B) -> bool {
now.saturating_add(One::one()) >= self.first.saturating_add(self.length.into())
}
pub fn update(&mut self, now: B) {
self.current = self.current.saturating_add(1u32);
self.first = now;
}
}
impl<
B: Copy
+ sp_std::ops::Add<Output = B>
+ sp_std::ops::Sub<Output = B>
+ From<u32>
+ PartialOrd
+ One
+ Zero
+ Saturating
+ CheckedMul,
> Default for RoundInfo<B>
{
fn default() -> RoundInfo<B> {
RoundInfo::new(0u32, Zero::zero(), 20u32)
}
}
pub(crate) type RoundIndex = u32;
type RewardPoint = u32;
#[cfg(feature = "runtime-benchmarks")]
pub trait StakingBenchmarkConfig: pallet_session::Config + pallet_issuance::Config {
type Balance;
type CurrencyId;
type RewardsApi: ProofOfStakeRewardsApi<Self::AccountId, Self::Balance, Self::CurrencyId>;
type Xyk: XykFunctionsTrait<Self::AccountId, Self::Balance, Self::CurrencyId>;
}
#[cfg(not(feature = "runtime-benchmarks"))]
pub trait StakingBenchmarkConfig {}
pub type BalanceOf<T> = <<T as Config>::Currency as MultiTokenCurrency<
<T as frame_system::Config>::AccountId,
>>::Balance;
pub type CurrencyIdOf<T> = <<T as Config>::Currency as MultiTokenCurrency<
<T as frame_system::Config>::AccountId,
>>::CurrencyId;
#[pallet::config]
pub trait Config: frame_system::Config + StakingBenchmarkConfig {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type StakingReservesProvider: StakingReservesProviderTrait<
Self::AccountId,
BalanceOf<Self>,
CurrencyIdOf<Self>,
>;
type Currency: MultiTokenCurrency<Self::AccountId>
+ MultiTokenReservableCurrency<Self::AccountId>
+ MultiTokenCurrencyExtended<Self::AccountId>;
type MonetaryGovernanceOrigin: EnsureOrigin<Self::RuntimeOrigin>;
#[pallet::constant]
type BlocksPerRound: Get<u32>;
#[pallet::constant]
type LeaveCandidatesDelay: Get<RoundIndex>;
#[pallet::constant]
type CandidateBondDelay: Get<RoundIndex>;
#[pallet::constant]
type LeaveDelegatorsDelay: Get<RoundIndex>;
#[pallet::constant]
type RevokeDelegationDelay: Get<RoundIndex>;
#[pallet::constant]
type DelegationBondDelay: Get<RoundIndex>;
#[pallet::constant]
type RewardPaymentDelay: Get<RoundIndex>;
#[pallet::constant]
type MinSelectedCandidates: Get<u32>;
#[pallet::constant]
type MaxCollatorCandidates: Get<u32>;
#[pallet::constant]
type MaxTotalDelegatorsPerCandidate: Get<u32>;
#[pallet::constant]
type MaxDelegatorsPerCandidate: Get<u32>;
#[pallet::constant]
type DefaultPayoutLimit: Get<u32>;
#[pallet::constant]
type MaxDelegationsPerDelegator: Get<u32>;
#[pallet::constant]
type DefaultCollatorCommission: Get<Perbill>;
#[pallet::constant]
type MinCollatorStk: Get<BalanceOf<Self>>;
#[pallet::constant]
type MinCandidateStk: Get<BalanceOf<Self>>;
#[pallet::constant]
type MinDelegation: Get<BalanceOf<Self>>;
#[pallet::constant]
type NativeTokenId: Get<CurrencyIdOf<Self>>;
type StakingLiquidityTokenValuator: Valuate<BalanceOf<Self>, CurrencyIdOf<Self>>;
type Issuance: ComputeIssuance + GetIssuance<BalanceOf<Self>>;
#[pallet::constant]
type StakingIssuanceVault: Get<Self::AccountId>;
type FallbackProvider: GetMembers<Self::AccountId>;
type WeightInfo: WeightInfo;
}
#[pallet::error]
pub enum Error<T> {
DelegatorDNE,
DelegatorDNEinTopNorBottom,
DelegatorDNEInDelegatorSet,
CandidateDNE,
DelegationDNE,
DelegatorExists,
CandidateExists,
CandidateBondBelowMin,
InsufficientBalance,
DelegationBelowMin,
AlreadyOffline,
AlreadyActive,
DelegatorAlreadyLeaving,
DelegatorNotLeaving,
DelegatorCannotLeaveYet,
CannotDelegateIfLeaving,
CandidateAlreadyLeaving,
CandidateNotLeaving,
CandidateCannotLeaveYet,
CannotGoOnlineIfLeaving,
ExceedMaxDelegationsPerDelegator,
AlreadyDelegatedCandidate,
InvalidSchedule,
CannotSetBelowMin,
NoWritingSameValue,
TooLowCandidateCountWeightHintJoinCandidates,
TooLowCandidateCountWeightHintCancelLeaveCandidates,
TooLowCandidateCountToLeaveCandidates,
TooLowDelegationCountToDelegate,
TooLowCandidateDelegationCountToDelegate,
TooLowDelegationCountToLeaveDelegators,
PendingCandidateRequestsDNE,
PendingCandidateRequestAlreadyExists,
PendingCandidateRequestNotDueYet,
PendingDelegationRequestDNE,
PendingDelegationRequestAlreadyExists,
PendingDelegationRequestNotDueYet,
StakingLiquidityTokenNotListed,
TooLowCurrentStakingLiquidityTokensCount,
StakingLiquidityTokenAlreadyListed,
ExceedMaxCollatorCandidates,
ExceedMaxTotalDelegatorsPerCandidate,
CandidateNotAggregating,
CandidateNotAggregatingUnderAggregator,
CandidateAlreadyApprovedByAggregator,
AggregatorExists,
CollatorRoundRewardsDNE,
DelegatorRewardsDNE,
AggregatorDNE,
TargettedAggregatorSameAsCurrent,
CandidateNotApprovedByAggregator,
AggregatorLiquidityTokenTaken,
IncorrectRewardDelegatorCount,
MathError,
}
#[pallet::event]
#[pallet::generate_deposit(pub(crate) fn deposit_event)]
pub enum Event<T: Config> {
NewRound(BlockNumberFor<T>, RoundIndex, u32, BalanceOf<T>),
JoinedCollatorCandidates(T::AccountId, BalanceOf<T>, BalanceOf<T>),
CollatorChosen(RoundIndex, T::AccountId, BalanceOf<T>),
CandidateBondMoreRequested(T::AccountId, BalanceOf<T>, RoundIndex),
CandidateBondLessRequested(T::AccountId, BalanceOf<T>, RoundIndex),
CandidateBondedMore(T::AccountId, BalanceOf<T>, BalanceOf<T>),
CandidateBondedLess(T::AccountId, BalanceOf<T>, BalanceOf<T>),
CandidateWentOffline(RoundIndex, T::AccountId),
CandidateBackOnline(RoundIndex, T::AccountId),
CandidateScheduledExit(RoundIndex, T::AccountId, RoundIndex),
CancelledCandidateExit(T::AccountId),
CancelledCandidateBondChange(T::AccountId, CandidateBondRequest<BalanceOf<T>>),
CandidateLeft(T::AccountId, BalanceOf<T>, BalanceOf<T>),
DelegationIncreaseScheduled(T::AccountId, T::AccountId, BalanceOf<T>, RoundIndex),
DelegationDecreaseScheduled(T::AccountId, T::AccountId, BalanceOf<T>, RoundIndex),
DelegationIncreased(T::AccountId, T::AccountId, BalanceOf<T>, bool),
DelegationDecreased(T::AccountId, T::AccountId, BalanceOf<T>, bool),
DelegatorExitScheduled(RoundIndex, T::AccountId, RoundIndex),
DelegationRevocationScheduled(RoundIndex, T::AccountId, T::AccountId, RoundIndex),
DelegatorLeft(T::AccountId, BalanceOf<T>),
DelegationRevoked(T::AccountId, T::AccountId, BalanceOf<T>),
DelegatorExitCancelled(T::AccountId),
CancelledDelegationRequest(T::AccountId, DelegationRequest<T::AccountId, BalanceOf<T>>),
Delegation(T::AccountId, BalanceOf<T>, T::AccountId, DelegatorAdded<BalanceOf<T>>),
DelegatorLeftCandidate(T::AccountId, T::AccountId, BalanceOf<T>, BalanceOf<T>),
DelegatorDueReward(RoundIndex, T::AccountId, T::AccountId, BalanceOf<T>),
Rewarded(RoundIndex, T::AccountId, BalanceOf<T>),
CollatorRewardsDistributed(T::AccountId, PayoutRounds),
StakeExpectationsSet(BalanceOf<T>, BalanceOf<T>, BalanceOf<T>),
TotalSelectedSet(u32, u32),
CollatorCommissionSet(Perbill, Perbill),
CandidateAggregatorUpdated(T::AccountId, Option<T::AccountId>),
AggregatorMetadataUpdated(T::AccountId),
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_idle(_now: BlockNumberFor<T>, remaining_weight: Weight) -> Weight {
let claim_cost = <T as Config>::WeightInfo::payout_collator_rewards();
if remaining_weight.ref_time() > claim_cost.ref_time() {
if let Some((collator, _round)) = RoundCollatorRewardInfo::<T>::iter_keys().next() {
let _ = Self::do_payout_collator_rewards(collator, Some(1));
}
}
claim_cost
}
}
#[pallet::storage]
#[pallet::getter(fn collator_commission)]
type CollatorCommission<T: Config> = StorageValue<_, Perbill, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn total_selected)]
pub(crate) type TotalSelected<T: Config> = StorageValue<_, u32, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn round)]
pub(crate) type Round<T: Config> = StorageValue<_, RoundInfo<BlockNumberFor<T>>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn delegator_state)]
pub(crate) type DelegatorState<T: Config> = StorageMap<
_,
Twox64Concat,
T::AccountId,
Delegator<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>,
OptionQuery,
>;
#[pallet::storage]
#[pallet::getter(fn candidate_state)]
pub(crate) type CandidateState<T: Config> = StorageMap<
_,
Twox64Concat,
T::AccountId,
CollatorCandidate<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>,
OptionQuery,
>;
#[pallet::storage]
#[pallet::getter(fn selected_candidates)]
type SelectedCandidates<T: Config> = StorageValue<_, Vec<T::AccountId>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn total)]
type Total<T: Config> = StorageMap<_, Twox64Concat, CurrencyIdOf<T>, BalanceOf<T>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn candidate_pool)]
type CandidatePool<T: Config> =
StorageValue<_, OrderedSet<Bond<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn at_stake)]
pub type AtStake<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
RoundIndex,
Twox64Concat,
T::AccountId,
CollatorSnapshot<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>,
ValueQuery,
>;
#[pallet::storage]
#[pallet::getter(fn points)]
pub type Points<T: Config> = StorageMap<_, Twox64Concat, RoundIndex, RewardPoint, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn awarded_pts)]
pub type AwardedPts<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
RoundIndex,
Twox64Concat,
T::AccountId,
RewardPoint,
ValueQuery,
>;
#[pallet::storage]
#[pallet::getter(fn staking_liquidity_tokens)]
pub type StakingLiquidityTokens<T: Config> = StorageValue<
_,
BTreeMap<CurrencyIdOf<T>, Option<(BalanceOf<T>, BalanceOf<T>)>>,
ValueQuery,
>;
#[pallet::storage]
#[pallet::getter(fn get_candidate_aggregator)]
pub type CandidateAggregator<T: Config> =
StorageValue<_, BTreeMap<T::AccountId, T::AccountId>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn get_aggregator_metadata)]
pub type AggregatorMetadata<T: Config> = StorageMap<
_,
Blake2_128Concat,
T::AccountId,
AggregatorMetadataType<T::AccountId, CurrencyIdOf<T>>,
OptionQuery,
>;
#[pallet::storage]
#[pallet::getter(fn get_round_aggregator_info)]
pub type RoundAggregatorInfo<T: Config> = StorageMap<
_,
Twox64Concat,
RoundIndex,
BTreeMap<T::AccountId, BTreeMap<T::AccountId, BalanceOf<T>>>,
OptionQuery,
>;
#[pallet::storage]
#[pallet::getter(fn get_round_collator_reward_info)]
pub type RoundCollatorRewardInfo<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::AccountId,
Twox64Concat,
RoundIndex,
RoundCollatorRewardInfoType<T::AccountId, BalanceOf<T>>,
OptionQuery,
>;
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct RoundCollatorRewardInfoType<AccountId, Balance> {
pub collator_reward: Balance,
pub delegator_rewards: BTreeMap<AccountId, Balance>,
}
impl<AccountId, Balance: Default> Default for RoundCollatorRewardInfoType<AccountId, Balance> {
fn default() -> RoundCollatorRewardInfoType<AccountId, Balance> {
Self { collator_reward: Default::default(), delegator_rewards: Default::default() }
}
}
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct AggregatorMetadataType<AccountId, CurrencyId> {
pub token_collator_map: BTreeMap<CurrencyId, AccountId>,
pub approved_candidates: BTreeSet<AccountId>,
}
impl<AccountId, CurrencyId: Default> Default for AggregatorMetadataType<AccountId, CurrencyId> {
fn default() -> AggregatorMetadataType<AccountId, CurrencyId> {
Self { token_collator_map: Default::default(), approved_candidates: Default::default() }
}
}
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub candidates: Vec<(T::AccountId, BalanceOf<T>, CurrencyIdOf<T>)>,
pub delegations: Vec<(T::AccountId, T::AccountId, BalanceOf<T>)>,
}
impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self {
Self { candidates: vec![], delegations: vec![] }
}
}
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
let mut liquidity_token_list: Vec<CurrencyIdOf<T>> = self
.candidates
.iter()
.cloned()
.map(|(_, _, l)| l)
.collect::<Vec<CurrencyIdOf<T>>>();
liquidity_token_list.sort();
liquidity_token_list.dedup();
let liquidity_token_count: u32 = liquidity_token_list.len().try_into().unwrap();
for (i, liquidity_token) in liquidity_token_list.iter().enumerate() {
if let Err(error) = <Pallet<T>>::add_staking_liquidity_token(
RawOrigin::Root.into(),
PairedOrLiquidityToken::Liquidity(*liquidity_token),
i as u32,
) {
log::warn!(
"Adding staking liquidity token failed in genesis with error {:?}",
error
);
}
}
let mut candidate_count = 0u32;
for &(ref candidate, balance, liquidity_token) in &self.candidates {
assert!(
<T as pallet::Config>::Currency::available_balance(
liquidity_token.into(),
candidate
) >= balance,
"Account does not have enough balance to bond as a candidate."
);
candidate_count = candidate_count.saturating_add(1u32);
if let Err(error) = <Pallet<T>>::join_candidates(
T::RuntimeOrigin::from(Some(candidate.clone()).into()),
balance,
liquidity_token,
None,
candidate_count,
liquidity_token_count,
) {
log::warn!("Join candidates failed in genesis with error {:?}", error);
} else {
candidate_count = candidate_count.saturating_add(1u32);
}
}
let mut col_delegator_count: BTreeMap<T::AccountId, u32> = BTreeMap::new();
let mut del_delegation_count: BTreeMap<T::AccountId, u32> = BTreeMap::new();
for &(ref delegator, ref target, balance) in &self.delegations {
let associated_collator = self.candidates.iter().find(|b| b.0 == *target);
let collator_liquidity_token =
associated_collator.expect("Delegation to non-existant collator").2;
assert!(
<T as pallet::Config>::Currency::available_balance(
collator_liquidity_token.into(),
delegator
) >= balance,
"Account does not have enough balance to place delegation."
);
let cd_count =
if let Some(x) = col_delegator_count.get(target) { *x } else { 0u32 };
let dd_count =
if let Some(x) = del_delegation_count.get(delegator) { *x } else { 0u32 };
if let Err(error) = <Pallet<T>>::delegate(
T::RuntimeOrigin::from(Some(delegator.clone()).into()),
target.clone(),
balance,
None,
cd_count,
dd_count,
) {
log::warn!("Delegate failed in genesis with error {:?}", error);
} else {
if let Some(x) = col_delegator_count.get_mut(target) {
*x = x.saturating_add(1u32);
} else {
col_delegator_count.insert(target.clone(), 1u32);
};
if let Some(x) = del_delegation_count.get_mut(delegator) {
*x = x.saturating_add(1u32);
} else {
del_delegation_count.insert(delegator.clone(), 1u32);
};
}
}
<CollatorCommission<T>>::put(T::DefaultCollatorCommission::get());
<TotalSelected<T>>::put(T::MinSelectedCandidates::get());
let (v_count, _, total_relevant_exposure) = <Pallet<T>>::select_top_candidates(1u32);
let round: RoundInfo<BlockNumberFor<T>> =
RoundInfo::new(0u32, 0u32.into(), <T as pallet::Config>::BlocksPerRound::get());
<Round<T>>::put(round);
for atstake in <AtStake<T>>::iter_prefix(1u32) {
<AtStake<T>>::insert(0u32, atstake.0, atstake.1);
}
<Pallet<T>>::deposit_event(Event::NewRound(
BlockNumberFor::<T>::zero(),
0u32,
v_count,
total_relevant_exposure,
));
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(<T as Config>::WeightInfo::set_total_selected())]
pub fn set_total_selected(origin: OriginFor<T>, new: u32) -> DispatchResultWithPostInfo {
frame_system::ensure_root(origin)?;
ensure!(new >= T::MinSelectedCandidates::get(), Error::<T>::CannotSetBelowMin);
let old = <TotalSelected<T>>::get();
ensure!(old != new, Error::<T>::NoWritingSameValue);
<TotalSelected<T>>::put(new);
Self::deposit_event(Event::TotalSelectedSet(old, new));
Ok(().into())
}
#[pallet::call_index(1)]
#[pallet::weight(<T as Config>::WeightInfo::set_collator_commission())]
pub fn set_collator_commission(
origin: OriginFor<T>,
new: Perbill,
) -> DispatchResultWithPostInfo {
frame_system::ensure_root(origin)?;
let old = <CollatorCommission<T>>::get();
ensure!(old != new, Error::<T>::NoWritingSameValue);
<CollatorCommission<T>>::put(new);
Self::deposit_event(Event::CollatorCommissionSet(old, new));
Ok(().into())
}
#[pallet::call_index(2)]
#[pallet::weight(<T as Config>::WeightInfo::join_candidates(*candidate_count, *liquidity_token_count))]
pub fn join_candidates(
origin: OriginFor<T>,
bond: BalanceOf<T>,
liquidity_token: CurrencyIdOf<T>,
use_balance_from: Option<BondKind>,
candidate_count: u32,
liquidity_token_count: u32,
) -> DispatchResultWithPostInfo {
let acc = ensure_signed(origin)?;
ensure!(!Self::is_candidate(&acc), Error::<T>::CandidateExists);
ensure!(!Self::is_delegator(&acc), Error::<T>::DelegatorExists);
ensure!(!Self::is_aggregator(&acc), Error::<T>::AggregatorExists);
let staking_liquidity_tokens = <StakingLiquidityTokens<T>>::get();
ensure!(
liquidity_token_count as usize >= staking_liquidity_tokens.len(),
Error::<T>::TooLowCurrentStakingLiquidityTokensCount
);
ensure!(
staking_liquidity_tokens.contains_key(&liquidity_token) ||
liquidity_token == T::NativeTokenId::get(),
Error::<T>::StakingLiquidityTokenNotListed
);
ensure!(
Self::valuate_bond(liquidity_token, bond) >= T::MinCandidateStk::get(),
Error::<T>::CandidateBondBelowMin
);
let mut candidates = <CandidatePool<T>>::get();
let old_count = candidates.0.len() as u32;
ensure!(
old_count < T::MaxCollatorCandidates::get(),
Error::<T>::ExceedMaxCollatorCandidates
);
ensure!(
candidate_count >= old_count,
Error::<T>::TooLowCandidateCountWeightHintJoinCandidates
);
ensure!(
candidates.insert(Bond { owner: acc.clone(), amount: bond, liquidity_token }),
Error::<T>::CandidateExists
);
<T as pallet::Config>::StakingReservesProvider::bond(
liquidity_token,
&acc,
bond,
use_balance_from,
)?;
let candidate = CollatorCandidate::new(acc.clone(), bond, liquidity_token);
<CandidateState<T>>::insert(&acc, candidate);
<CandidatePool<T>>::put(candidates);
let new_total = <Total<T>>::get(liquidity_token).saturating_add(bond);
<Total<T>>::insert(liquidity_token, new_total);
Self::deposit_event(Event::JoinedCollatorCandidates(acc, bond, new_total));
Ok(().into())
}
#[pallet::call_index(3)]
#[pallet::weight(<T as Config>::WeightInfo::schedule_leave_candidates(*candidate_count))]
pub fn schedule_leave_candidates(
origin: OriginFor<T>,
candidate_count: u32,
) -> DispatchResultWithPostInfo {
let collator = ensure_signed(origin)?;
let mut state = <CandidateState<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
let (now, when) = state.leave::<T>()?;
let mut candidates = <CandidatePool<T>>::get();
ensure!(
candidate_count >= candidates.0.len() as u32,
Error::<T>::TooLowCandidateCountToLeaveCandidates
);
if candidates.remove(&Bond::from_owner(collator.clone())) {
<CandidatePool<T>>::put(candidates);
}
<CandidateState<T>>::insert(&collator, state);
Self::deposit_event(Event::CandidateScheduledExit(now, collator, when));
Ok(().into())
}
#[pallet::call_index(4)]
#[pallet::weight(<T as Config>::WeightInfo::execute_leave_candidates(*candidate_delegation_count))]
pub fn execute_leave_candidates(
origin: OriginFor<T>,
candidate: T::AccountId,
candidate_delegation_count: u32,
) -> DispatchResultWithPostInfo {
ensure_signed(origin)?;
let state = <CandidateState<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
ensure!(
state.delegators.0.len() <= candidate_delegation_count as usize,
Error::<T>::TooLowCandidateCountToLeaveCandidates
);
state.can_leave::<T>()?;
let return_stake = |bond: Bond<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>| {
let debug_amount = <T as pallet::Config>::StakingReservesProvider::unbond(
bond.liquidity_token.into(),
&bond.owner,
bond.amount,
);
if !debug_amount.is_zero() {
log::warn!("Unbond in staking returned non-zero value {:?}", debug_amount);
}
let mut delegator = DelegatorState::<T>::get(&bond.owner).expect(
"Collator state and delegator state are consistent.
Collator state has a record of this delegation. Therefore,
Delegator state also has a record. qed.",
);
if let Some(remaining_delegations) = delegator.rm_delegation(candidate.clone()) {
if remaining_delegations.is_zero() {
<DelegatorState<T>>::remove(&bond.owner);
} else {
let _ = delegator.requests.requests.remove(&candidate);
<DelegatorState<T>>::insert(&bond.owner, delegator);
}
}
};
for bond in state.top_delegations {
return_stake(bond);
}
for bond in state.bottom_delegations {
return_stake(bond);
}
let debug_amount = <T as pallet::Config>::StakingReservesProvider::unbond(
state.liquidity_token.into(),
&state.id,
state.bond,
);
if !debug_amount.is_zero() {
log::warn!("Unbond in staking returned non-zero value {:?}", debug_amount);
}
let res = Self::do_update_candidate_aggregator(&candidate, None);
match res {
Err(e) if e == DispatchError::from(Error::<T>::CandidateNotAggregating) => {},
Err(_) => {
log::error!("do_update_candidate_aggregator failed with error {:?}", res);
},
Ok(_) => {},
}
<CandidateState<T>>::remove(&candidate);
let new_total_staked =
<Total<T>>::get(state.liquidity_token).saturating_sub(state.total_backing);
<Total<T>>::insert(state.liquidity_token, new_total_staked);
Self::deposit_event(Event::CandidateLeft(
candidate,
state.total_backing,
new_total_staked,
));
Ok(().into())
}
#[pallet::call_index(5)]
#[pallet::weight(<T as Config>::WeightInfo::cancel_leave_candidates(*candidate_count))]
pub fn cancel_leave_candidates(
origin: OriginFor<T>,
candidate_count: u32,
) -> DispatchResultWithPostInfo {
let collator = ensure_signed(origin)?;
let mut state = <CandidateState<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
ensure!(state.is_leaving(), Error::<T>::CandidateNotLeaving);
state.go_online();
let mut candidates = <CandidatePool<T>>::get();
ensure!(
candidates.0.len() < T::MaxCollatorCandidates::get() as usize,
Error::<T>::ExceedMaxCollatorCandidates
);
ensure!(
candidates.0.len() as u32 <= candidate_count,
Error::<T>::TooLowCandidateCountWeightHintCancelLeaveCandidates
);
ensure!(
candidates.insert(Bond {
owner: collator.clone(),
amount: state.total_counted,
liquidity_token: state.liquidity_token
}),
Error::<T>::AlreadyActive
);
<CandidatePool<T>>::put(candidates);
<CandidateState<T>>::insert(&collator, state);
Self::deposit_event(Event::CancelledCandidateExit(collator));
Ok(().into())
}
#[pallet::call_index(6)]
#[pallet::weight(<T as Config>::WeightInfo::go_offline())]
pub fn go_offline(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
let collator = ensure_signed(origin)?;
let mut state = <CandidateState<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
ensure!(state.is_active(), Error::<T>::AlreadyOffline);
state.go_offline();
let mut candidates = <CandidatePool<T>>::get();
if candidates.remove(&Bond::from_owner(collator.clone())) {
<CandidatePool<T>>::put(candidates);
}
<CandidateState<T>>::insert(&collator, state);
Self::deposit_event(Event::CandidateWentOffline(<Round<T>>::get().current, collator));
Ok(().into())
}
#[pallet::call_index(7)]
#[pallet::weight(<T as Config>::WeightInfo::go_online())]
pub fn go_online(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
let collator = ensure_signed(origin)?;
let mut state = <CandidateState<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
ensure!(!state.is_active(), Error::<T>::AlreadyActive);
ensure!(!state.is_leaving(), Error::<T>::CannotGoOnlineIfLeaving);
state.go_online();
let mut candidates = <CandidatePool<T>>::get();
ensure!(
candidates.0.len() < T::MaxCollatorCandidates::get() as usize,
Error::<T>::ExceedMaxCollatorCandidates
);
ensure!(
candidates.insert(Bond {
owner: collator.clone(),
amount: state.total_counted,
liquidity_token: state.liquidity_token
}),
Error::<T>::AlreadyActive
);
<CandidatePool<T>>::put(candidates);
<CandidateState<T>>::insert(&collator, state);
Self::deposit_event(Event::CandidateBackOnline(<Round<T>>::get().current, collator));
Ok(().into())
}
#[pallet::call_index(8)]
#[pallet::weight(<T as Config>::WeightInfo::schedule_candidate_bond_more())]
pub fn schedule_candidate_bond_more(
origin: OriginFor<T>,
more: BalanceOf<T>,
use_balance_from: Option<BondKind>,
) -> DispatchResultWithPostInfo {
let collator = ensure_signed(origin)?;
let mut state = <CandidateState<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
let when = state.schedule_bond_more::<T>(more, use_balance_from)?;
<CandidateState<T>>::insert(&collator, state);
Self::deposit_event(Event::CandidateBondMoreRequested(collator, more, when));
Ok(().into())
}
#[pallet::call_index(9)]
#[pallet::weight(<T as Config>::WeightInfo::schedule_candidate_bond_less())]
pub fn schedule_candidate_bond_less(
origin: OriginFor<T>,
less: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let collator = ensure_signed(origin)?;
let mut state = <CandidateState<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
let when = state.schedule_bond_less::<T>(less)?;
<CandidateState<T>>::insert(&collator, state);
Self::deposit_event(Event::CandidateBondLessRequested(collator, less, when));
Ok(().into())
}
#[pallet::call_index(10)]
#[pallet::weight(<T as Config>::WeightInfo::execute_candidate_bond_more())]
pub fn execute_candidate_bond_request(
origin: OriginFor<T>,
candidate: T::AccountId,
use_balance_from: Option<BondKind>,
) -> DispatchResultWithPostInfo {
ensure_signed(origin)?; let mut state = <CandidateState<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
let event = state.execute_pending_request::<T>(use_balance_from)?;
<CandidateState<T>>::insert(&candidate, state);
Self::deposit_event(event);
Ok(().into())
}
#[pallet::call_index(11)]
#[pallet::weight(<T as Config>::WeightInfo::cancel_candidate_bond_more())]
pub fn cancel_candidate_bond_request(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
let collator = ensure_signed(origin)?;
let mut state = <CandidateState<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
let event = state.cancel_pending_request::<T>()?;
<CandidateState<T>>::insert(&collator, state);
Self::deposit_event(event);
Ok(().into())
}
#[pallet::call_index(12)]
#[pallet::weight(
<T as Config>::WeightInfo::delegate(
*candidate_delegation_count,
*delegation_count,
)
)]
pub fn delegate(
origin: OriginFor<T>,
collator: T::AccountId,
amount: BalanceOf<T>,
use_balance_from: Option<BondKind>,
candidate_delegation_count: u32,
delegation_count: u32,
) -> DispatchResultWithPostInfo {
let acc = ensure_signed(origin)?;
ensure!(!Self::is_aggregator(&acc), Error::<T>::AggregatorExists);
let mut collator_state =
<CandidateState<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
let delegator_state = if let Some(mut state) = <DelegatorState<T>>::get(&acc) {
ensure!(state.is_active(), Error::<T>::CannotDelegateIfLeaving);
ensure!(
Self::valuate_bond(collator_state.liquidity_token, amount) >=
T::MinDelegation::get(),
Error::<T>::DelegationBelowMin
);
ensure!(
delegation_count >= state.delegations.0.len() as u32,
Error::<T>::TooLowDelegationCountToDelegate
);
ensure!(
(state.delegations.0.len() as u32) < T::MaxDelegationsPerDelegator::get(),
Error::<T>::ExceedMaxDelegationsPerDelegator
);
ensure!(
state.add_delegation(Bond {
owner: collator.clone(),
amount,
liquidity_token: collator_state.liquidity_token,
}),
Error::<T>::AlreadyDelegatedCandidate
);
state
} else {
ensure!(amount >= T::MinDelegation::get(), Error::<T>::DelegationBelowMin);
ensure!(!Self::is_candidate(&acc), Error::<T>::CandidateExists);
Delegator::new(
acc.clone(),
collator.clone(),
amount,
collator_state.liquidity_token,
)
};
ensure!(
collator_state.delegators.0.len() <
T::MaxTotalDelegatorsPerCandidate::get() as usize,
Error::<T>::ExceedMaxTotalDelegatorsPerCandidate
);
ensure!(
candidate_delegation_count >= collator_state.delegators.0.len() as u32,
Error::<T>::TooLowCandidateDelegationCountToDelegate
);
let delegator_position = collator_state.add_delegation::<T>(acc.clone(), amount)?;
<T as pallet::Config>::StakingReservesProvider::bond(
collator_state.liquidity_token.into(),
&acc,
amount,
use_balance_from,
)?;
if let DelegatorAdded::AddedToTop { new_total } = delegator_position {
if collator_state.is_active() {
Self::update_active(
collator.clone(),
new_total,
collator_state.liquidity_token,
);
}
}
let new_total_locked =
<Total<T>>::get(collator_state.liquidity_token).saturating_add(amount);
<Total<T>>::insert(collator_state.liquidity_token, new_total_locked);
<CandidateState<T>>::insert(&collator, collator_state);
<DelegatorState<T>>::insert(&acc, delegator_state);
Self::deposit_event(Event::Delegation(acc, amount, collator, delegator_position));
Ok(().into())
}
#[pallet::call_index(13)]
#[pallet::weight(<T as Config>::WeightInfo::schedule_leave_delegators())]
pub fn schedule_leave_delegators(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
let acc = ensure_signed(origin)?;
let mut state = <DelegatorState<T>>::get(&acc).ok_or(Error::<T>::DelegatorDNE)?;
ensure!(!state.is_leaving(), Error::<T>::DelegatorAlreadyLeaving);
let (now, when) = state.schedule_leave::<T>();
<DelegatorState<T>>::insert(&acc, state);
Self::deposit_event(Event::DelegatorExitScheduled(now, acc, when));
Ok(().into())
}
#[pallet::call_index(14)]
#[pallet::weight(<T as Config>::WeightInfo::execute_leave_delegators(*delegation_count))]
pub fn execute_leave_delegators(
origin: OriginFor<T>,
delegator: T::AccountId,
delegation_count: u32,
) -> DispatchResultWithPostInfo {
ensure_signed(origin)?;
let state = <DelegatorState<T>>::get(&delegator).ok_or(Error::<T>::DelegatorDNE)?;
state.can_execute_leave::<T>(delegation_count)?;
let mut amount_unstaked: BalanceOf<T> = Zero::zero();
for bond in state.delegations.0 {
amount_unstaked = amount_unstaked.saturating_add(bond.amount);
if let Err(error) =
Self::delegator_leaves_collator(delegator.clone(), bond.owner.clone())
{
log::warn!(
"STORAGE CORRUPTED \nDelegator leaving collator failed with error: {:?}",
error
);
}
}
<DelegatorState<T>>::remove(&delegator);
Self::deposit_event(Event::DelegatorLeft(delegator, amount_unstaked));
Ok(().into())
}
#[pallet::call_index(15)]
#[pallet::weight(<T as Config>::WeightInfo::cancel_leave_delegators())]
pub fn cancel_leave_delegators(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
let delegator = ensure_signed(origin)?;
let mut state = <DelegatorState<T>>::get(&delegator).ok_or(Error::<T>::DelegatorDNE)?;
ensure!(state.is_leaving(), Error::<T>::DelegatorDNE);
state.cancel_leave();
<DelegatorState<T>>::insert(&delegator, state);
Self::deposit_event(Event::DelegatorExitCancelled(delegator));
Ok(().into())
}
#[pallet::call_index(16)]
#[pallet::weight(<T as Config>::WeightInfo::schedule_revoke_delegation())]
pub fn schedule_revoke_delegation(
origin: OriginFor<T>,
collator: T::AccountId,
) -> DispatchResultWithPostInfo {
let delegator = ensure_signed(origin)?;
let mut state = <DelegatorState<T>>::get(&delegator).ok_or(Error::<T>::DelegatorDNE)?;
let (now, when) = state.schedule_revoke::<T>(collator.clone())?;
<DelegatorState<T>>::insert(&delegator, state);
Self::deposit_event(Event::DelegationRevocationScheduled(
now, delegator, collator, when,
));
Ok(().into())
}
#[pallet::call_index(17)]
#[pallet::weight(<T as Config>::WeightInfo::schedule_delegator_bond_more())]
pub fn schedule_delegator_bond_more(
origin: OriginFor<T>,
candidate: T::AccountId,
more: BalanceOf<T>,
use_balance_from: Option<BondKind>,
) -> DispatchResultWithPostInfo {
let delegator = ensure_signed(origin)?;
let mut state = <DelegatorState<T>>::get(&delegator).ok_or(Error::<T>::DelegatorDNE)?;
let when = state.schedule_increase_delegation::<T>(
candidate.clone(),
more,
use_balance_from,
)?;
<DelegatorState<T>>::insert(&delegator, state);
Self::deposit_event(Event::DelegationIncreaseScheduled(
delegator, candidate, more, when,
));
Ok(().into())
}
#[pallet::call_index(18)]
#[pallet::weight(<T as Config>::WeightInfo::schedule_delegator_bond_less())]
pub fn schedule_delegator_bond_less(
origin: OriginFor<T>,
candidate: T::AccountId,
less: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let caller = ensure_signed(origin)?;
let mut state = <DelegatorState<T>>::get(&caller).ok_or(Error::<T>::DelegatorDNE)?;
let when = state.schedule_decrease_delegation::<T>(candidate.clone(), less)?;
<DelegatorState<T>>::insert(&caller, state);
Self::deposit_event(Event::DelegationDecreaseScheduled(caller, candidate, less, when));
Ok(().into())
}
#[pallet::call_index(19)]
#[pallet::weight(<T as Config>::WeightInfo::execute_delegator_bond_more())]
pub fn execute_delegation_request(
origin: OriginFor<T>,
delegator: T::AccountId,
candidate: T::AccountId,
use_balance_from: Option<BondKind>,
) -> DispatchResultWithPostInfo {
ensure_signed(origin)?; let mut state = <DelegatorState<T>>::get(&delegator).ok_or(Error::<T>::DelegatorDNE)?;
state.execute_pending_request::<T>(candidate, use_balance_from)?;
Ok(().into())
}
#[pallet::call_index(20)]
#[pallet::weight(<T as Config>::WeightInfo::cancel_delegator_bond_more())]
pub fn cancel_delegation_request(
origin: OriginFor<T>,
candidate: T::AccountId,
) -> DispatchResultWithPostInfo {
let delegator = ensure_signed(origin)?;
let mut state = <DelegatorState<T>>::get(&delegator).ok_or(Error::<T>::DelegatorDNE)?;
let request = state.cancel_pending_request::<T>(candidate)?;
<DelegatorState<T>>::insert(&delegator, state);
Self::deposit_event(Event::CancelledDelegationRequest(delegator, request));
Ok(().into())
}
#[pallet::call_index(21)]
#[pallet::weight(<T as Config>::WeightInfo::add_staking_liquidity_token(*current_liquidity_tokens))]
pub fn add_staking_liquidity_token(
origin: OriginFor<T>,
paired_or_liquidity_token: PairedOrLiquidityToken<CurrencyIdOf<T>>,
current_liquidity_tokens: u32,
) -> DispatchResultWithPostInfo {
ensure_root(origin)?;
let added_liquidity_token: CurrencyIdOf<T> = match paired_or_liquidity_token {
PairedOrLiquidityToken::Paired(x) =>
T::StakingLiquidityTokenValuator::get_liquidity_asset(
x.into(),
T::NativeTokenId::get().into(),
)?,
PairedOrLiquidityToken::Liquidity(x) => {
T::StakingLiquidityTokenValuator::get_liquidity_token_mga_pool(x.into())?;
x
},
};
StakingLiquidityTokens::<T>::try_mutate(
|staking_liquidity_tokens| -> DispatchResult {
ensure!(
current_liquidity_tokens as usize >= staking_liquidity_tokens.len(),
Error::<T>::TooLowCurrentStakingLiquidityTokensCount
);
ensure!(
staking_liquidity_tokens.insert(added_liquidity_token, None).is_none(),
Error::<T>::StakingLiquidityTokenAlreadyListed
);
Ok(())
},
)?;
Ok(().into())
}
#[pallet::call_index(22)]
#[pallet::weight(<T as Config>::WeightInfo::remove_staking_liquidity_token(*current_liquidity_tokens))]
pub fn remove_staking_liquidity_token(
origin: OriginFor<T>,
paired_or_liquidity_token: PairedOrLiquidityToken<CurrencyIdOf<T>>,
current_liquidity_tokens: u32,
) -> DispatchResultWithPostInfo {
ensure_root(origin)?;
let removed_liquidity_token: CurrencyIdOf<T> = match paired_or_liquidity_token {
PairedOrLiquidityToken::Paired(x) =>
T::StakingLiquidityTokenValuator::get_liquidity_asset(
x.into(),
T::NativeTokenId::get().into(),
)?,
PairedOrLiquidityToken::Liquidity(x) => x,
};
StakingLiquidityTokens::<T>::try_mutate(
|staking_liquidity_tokens| -> DispatchResult {
ensure!(
current_liquidity_tokens as usize >= staking_liquidity_tokens.len(),
Error::<T>::TooLowCurrentStakingLiquidityTokensCount
);
ensure!(
staking_liquidity_tokens.remove(&removed_liquidity_token).is_some(),
Error::<T>::StakingLiquidityTokenNotListed
);
Ok(())
},
)?;
Ok(().into())
}
#[pallet::call_index(23)]
#[pallet::weight(T::DbWeight::get().reads_writes(20, 20))]
#[transactional]
pub fn aggregator_update_metadata(
origin: OriginFor<T>,
collator_candidates: Vec<T::AccountId>,
action: MetadataUpdateAction,
) -> DispatchResultWithPostInfo {
let aggregator = ensure_signed(origin)?;
ensure!(!Self::is_candidate(&aggregator), Error::<T>::CandidateExists);
ensure!(!Self::is_delegator(&aggregator), Error::<T>::DelegatorExists);
AggregatorMetadata::<T>::try_mutate_exists(
aggregator.clone(),
|maybe_aggregator_metadata| -> DispatchResult {
let mut aggregator_metadata =
maybe_aggregator_metadata.take().unwrap_or_default();
match action {
MetadataUpdateAction::ExtendApprovedCollators => {
collator_candidates.iter().try_for_each(
|collator| -> DispatchResult {
Self::add_approved_candidate_for_collator_metadata(
collator,
&mut aggregator_metadata,
)
},
)?;
},
MetadataUpdateAction::RemoveApprovedCollators => {
collator_candidates.iter().try_for_each(
|collator| -> DispatchResult {
Self::remove_approved_candidates_from_collator_metadata(
collator,
&aggregator,
&mut aggregator_metadata,
)
},
)?;
},
}
if !aggregator_metadata.approved_candidates.is_empty() {
*maybe_aggregator_metadata = Some(aggregator_metadata);
}
Ok(())
},
)?;
Self::deposit_event(Event::AggregatorMetadataUpdated(aggregator));
Ok(().into())
}
#[pallet::call_index(24)]
#[pallet::weight(T::DbWeight::get().reads_writes(20, 20))]
#[transactional]
pub fn update_candidate_aggregator(
origin: OriginFor<T>,
maybe_aggregator: Option<T::AccountId>,
) -> DispatchResultWithPostInfo {
let candidate = ensure_signed(origin)?;
Self::do_update_candidate_aggregator(&candidate, maybe_aggregator.clone())?;
Ok(().into())
}
#[pallet::call_index(25)]
#[pallet::weight(number_of_sesisons.unwrap_or(T::DefaultPayoutLimit::get()) * <T as Config>::WeightInfo::payout_collator_rewards())]
#[transactional]
pub fn payout_collator_rewards(
origin: OriginFor<T>,
collator: T::AccountId,
number_of_sesisons: Option<u32>,
) -> DispatchResultWithPostInfo {
let _caller = ensure_signed(origin)?;
Self::do_payout_collator_rewards(collator, number_of_sesisons)
}
#[pallet::call_index(26)]
#[pallet::weight(<T as Config>::WeightInfo::payout_delegator_reward())]
#[transactional]
pub fn payout_delegator_reward(
origin: OriginFor<T>,
round: RoundIndex,
collator: T::AccountId,
delegator: T::AccountId,
) -> DispatchResultWithPostInfo {
let _caller = ensure_signed(origin)?;
RoundCollatorRewardInfo::<T>::try_mutate(
collator.clone(),
round,
|maybe_collator_payout_info| -> DispatchResult {
let collator_payout_info = maybe_collator_payout_info
.as_mut()
.ok_or(Error::<T>::CollatorRoundRewardsDNE)?;
let delegator_reward = collator_payout_info
.delegator_rewards
.remove(&delegator)
.ok_or(Error::<T>::DelegatorRewardsDNE)?;
Self::payout_reward(
round,
delegator,
delegator_reward,
RewardKind::Delegator(collator),
)?;
Ok(())
},
)?;
Ok(().into())
}
}
impl<T: Config> Pallet<T> {
fn add_approved_candidate_for_collator_metadata(
collator_candidate: &T::AccountId,
aggregator_metadata: &mut AggregatorMetadataType<T::AccountId, CurrencyIdOf<T>>,
) -> DispatchResult {
ensure!(Self::is_candidate(&collator_candidate), Error::<T>::CandidateDNE);
ensure!(
aggregator_metadata.approved_candidates.insert(collator_candidate.clone()),
Error::<T>::CandidateAlreadyApprovedByAggregator
);
Ok(())
}
fn remove_approved_candidates_from_collator_metadata(
collator_candidate: &T::AccountId,
aggregator: &T::AccountId,
aggregator_metadata: &mut AggregatorMetadataType<T::AccountId, CurrencyIdOf<T>>,
) -> DispatchResult {
let _ = CandidateAggregator::<T>::try_mutate(
|candidate_aggregator_map| -> DispatchResult {
let collator_candidate_state = <CandidateState<T>>::get(&collator_candidate)
.ok_or(Error::<T>::CandidateDNE)?;
ensure!(
Some(aggregator.clone()) ==
candidate_aggregator_map.remove(collator_candidate),
Error::<T>::CandidateNotAggregatingUnderAggregator
);
let res = aggregator_metadata
.token_collator_map
.remove(&collator_candidate_state.liquidity_token);
if res != Some(collator_candidate.clone()) {
log::error!(
"Inconsistent aggregator metadata: candidate - {:?}, aggregator - {:?}",
collator_candidate,
aggregator
);
}
Ok(())
},
);
ensure!(
aggregator_metadata.approved_candidates.remove(collator_candidate),
Error::<T>::CandidateNotApprovedByAggregator
);
Ok(())
}
pub fn payout_reward(
round: RoundIndex,
to: T::AccountId,
amt: BalanceOf<T>,
kind: RewardKind<T::AccountId>,
) -> DispatchResult {
let _ = <T as pallet::Config>::Currency::transfer(
T::NativeTokenId::get().into(),
&<T as pallet::Config>::StakingIssuanceVault::get(),
&to,
amt,
ExistenceRequirement::AllowDeath,
)?;
match kind {
RewardKind::Collator =>
Self::deposit_event(Event::Rewarded(round, to.clone(), amt)),
RewardKind::Delegator(collator) => Self::deposit_event(Event::DelegatorDueReward(
round,
collator.clone(),
to.clone(),
amt,
)),
};
Ok(())
}
pub fn is_delegator(acc: &T::AccountId) -> bool {
<DelegatorState<T>>::get(acc).is_some()
}
pub fn is_candidate(acc: &T::AccountId) -> bool {
<CandidateState<T>>::get(acc).is_some()
}
pub fn is_aggregator(acc: &T::AccountId) -> bool {
<AggregatorMetadata<T>>::get(acc).is_some()
}
pub fn is_selected_candidate(acc: &T::AccountId) -> bool {
<SelectedCandidates<T>>::get().binary_search(acc).is_ok()
}
fn remove_aggregator_for_collator(candidate: &T::AccountId) -> DispatchResult {
CandidateAggregator::<T>::try_mutate(|candidate_aggregator_info| -> DispatchResult {
let detached_aggregator = candidate_aggregator_info
.remove(&candidate)
.ok_or(Error::<T>::CandidateNotAggregating)?;
AggregatorMetadata::<T>::try_mutate(
detached_aggregator.clone(),
|maybe_aggregator_metadata| -> DispatchResult {
let aggregator_metadata =
maybe_aggregator_metadata.as_mut().ok_or(Error::<T>::AggregatorDNE)?;
let candidate_state =
<CandidateState<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
let res = aggregator_metadata
.token_collator_map
.remove(&candidate_state.liquidity_token);
if res != Some(candidate.clone()) {
log::error!(
"Inconsistent aggregator metadata: candidate - {:?}, aggregator - {:?}",
candidate,
detached_aggregator
);
}
Ok(())
},
)?;
Ok(())
})?;
Ok(())
}
fn corelate_collator_with_aggregator(
candidate: &T::AccountId,
new_aggregator: T::AccountId,
) -> DispatchResult {
AggregatorMetadata::<T>::try_mutate(
new_aggregator,
|maybe_aggregator_metadata| -> DispatchResult {
let aggregator_metadata =
maybe_aggregator_metadata.as_mut().ok_or(Error::<T>::AggregatorDNE)?;
ensure!(
aggregator_metadata.approved_candidates.contains(candidate),
Error::<T>::CandidateNotApprovedByAggregator
);
let candidate_state =
<CandidateState<T>>::get(candidate).ok_or(Error::<T>::CandidateDNE)?;
ensure!(
aggregator_metadata
.token_collator_map
.insert(candidate_state.liquidity_token, candidate.clone())
.is_none(),
Error::<T>::AggregatorLiquidityTokenTaken
);
Ok(())
},
)?;
Ok(())
}
fn replace_aggregator_for_collator(
candidate: &T::AccountId,
new_aggregator: T::AccountId,
prev_aggregator: T::AccountId,
) -> DispatchResult {
ensure!(
prev_aggregator != new_aggregator,
Error::<T>::TargettedAggregatorSameAsCurrent
);
AggregatorMetadata::<T>::try_mutate(
prev_aggregator.clone(),
|maybe_prev_aggregator_metadata| -> DispatchResult {
let prev_aggregator_metadata =
maybe_prev_aggregator_metadata.as_mut().ok_or(Error::<T>::AggregatorDNE)?;
let candidate_state =
<CandidateState<T>>::get(candidate).ok_or(Error::<T>::CandidateDNE)?;
let res = prev_aggregator_metadata
.token_collator_map
.remove(&candidate_state.liquidity_token);
if res != Some(candidate.clone()) {
log::error!(
"Inconsistent aggregator metadata: candidate - {:?}, aggregator - {:?}",
candidate,
prev_aggregator
);
}
Self::corelate_collator_with_aggregator(candidate, new_aggregator)?;
Ok(())
},
)?;
Ok(())
}
fn assign_aggregator_for_collator(
candidate: &T::AccountId,
new_aggregator: T::AccountId,
) -> DispatchResult {
CandidateAggregator::<T>::try_mutate(|candidate_aggregator_info| -> DispatchResult {
match candidate_aggregator_info.insert(candidate.clone(), new_aggregator.clone()) {
Some(prev_aggregator) => {
Self::replace_aggregator_for_collator(
candidate,
new_aggregator,
prev_aggregator,
)?;
},
None => {
Self::corelate_collator_with_aggregator(candidate, new_aggregator)?;
},
}
Ok(())
})?;
Ok(())
}
pub fn do_update_candidate_aggregator(
candidate: &T::AccountId,
maybe_aggregator: Option<T::AccountId>,
) -> DispatchResult {
ensure!(Self::is_candidate(candidate), Error::<T>::CandidateDNE);
if let Some(ref new_aggregator) = maybe_aggregator {
Self::assign_aggregator_for_collator(candidate, new_aggregator.clone())?;
} else {
Self::remove_aggregator_for_collator(candidate)?;
}
Self::deposit_event(Event::CandidateAggregatorUpdated(
candidate.clone(),
maybe_aggregator,
));
Ok(())
}
fn update_active(
candidate: T::AccountId,
total: BalanceOf<T>,
candidate_liquidity_token: CurrencyIdOf<T>,
) {
let mut candidates = <CandidatePool<T>>::get();
candidates.remove(&Bond::from_owner(candidate.clone()));
candidates.insert(Bond {
owner: candidate,
amount: total,
liquidity_token: candidate_liquidity_token,
});
<CandidatePool<T>>::put(candidates);
}
fn delegator_leaves_collator(
delegator: T::AccountId,
collator: T::AccountId,
) -> DispatchResult {
let mut state = <CandidateState<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
let (total_changed, delegator_stake) = state.rm_delegator::<T>(delegator.clone())?;
let debug_amount = <T as pallet::Config>::StakingReservesProvider::unbond(
state.liquidity_token.into(),
&delegator,
delegator_stake,
);
if !debug_amount.is_zero() {
log::warn!("Unbond in staking returned non-zero value {:?}", debug_amount);
}
if state.is_active() && total_changed {
Self::update_active(collator.clone(), state.total_counted, state.liquidity_token);
}
let new_total_locked =
<Total<T>>::get(state.liquidity_token).saturating_sub(delegator_stake);
<Total<T>>::insert(state.liquidity_token, new_total_locked);
let new_total = state.total_counted;
<CandidateState<T>>::insert(&collator, state);
Self::deposit_event(Event::DelegatorLeftCandidate(
delegator,
collator,
delegator_stake,
new_total,
));
Ok(())
}
fn process_collator_with_rewards(
round_to_payout: u32,
collator: T::AccountId,
reward: BalanceOf<T>,
) {
let state = <AtStake<T>>::take(round_to_payout, &collator);
let collator_commission_perbill = <CollatorCommission<T>>::get();
let mut collator_payout_info =
RoundCollatorRewardInfoType::<T::AccountId, BalanceOf<T>>::default();
if state.delegations.is_empty() {
collator_payout_info.collator_reward = reward;
RoundCollatorRewardInfo::<T>::insert(
collator,
round_to_payout,
collator_payout_info,
);
} else {
let collator_commission = collator_commission_perbill.mul_floor(reward);
let reward_less_commission = reward.saturating_sub(collator_commission);
let collator_perbill = Perbill::from_rational(state.bond, state.total);
let collator_reward_less_commission =
collator_perbill.mul_floor(reward_less_commission);
collator_payout_info.collator_reward =
collator_reward_less_commission.saturating_add(collator_commission);
match state
.delegations
.iter()
.cloned()
.try_fold(state.bond, |acc, x| acc.checked_add(&x.amount))
{
Some(total) if total <= state.total => {
state.delegations.iter().for_each(|delegator_bond| {
collator_payout_info.delegator_rewards.insert(
delegator_bond.owner.clone(),
multiply_by_rational_with_rounding(
reward_less_commission.into(),
delegator_bond.amount.into(),
state.total.into(),
Rounding::Down,
)
.map(SaturatedConversion::saturated_into)
.unwrap_or(BalanceOf::<T>::zero()),
);
});
},
_ => {
let delegator_count = state.delegations.len() as u32;
let delegator_reward = reward
.saturating_sub(collator_payout_info.collator_reward)
.checked_div(&delegator_count.into())
.unwrap_or(Zero::zero());
state.delegations.iter().for_each(|delegator_bond| {
collator_payout_info
.delegator_rewards
.insert(delegator_bond.owner.clone(), delegator_reward);
});
},
}
RoundCollatorRewardInfo::<T>::insert(
collator,
round_to_payout,
collator_payout_info,
);
}
}
fn process_aggregator_with_rewards_and_dist(
round_to_payout: u32,
_aggregator: T::AccountId,
author_rewards: BalanceOf<T>,
distribution: &BTreeMap<T::AccountId, BalanceOf<T>>,
) {
match distribution
.values()
.cloned()
.try_fold(BalanceOf::<T>::zero(), |acc, x| acc.checked_add(&x))
{
Some(aggregator_total_valuation) => {
distribution.iter().for_each(|(collator, contribution)| {
Self::process_collator_with_rewards(
round_to_payout,
collator.clone(),
multiply_by_rational_with_rounding(
author_rewards.into(),
contribution.clone().into(),
aggregator_total_valuation.into(),
Rounding::Down,
)
.map(SaturatedConversion::saturated_into)
.unwrap_or(BalanceOf::<T>::zero()),
)
});
},
None => {
let collator_count = distribution.keys().cloned().count() as u32;
let collator_reward = author_rewards
.checked_div(&collator_count.into())
.unwrap_or(BalanceOf::<T>::zero());
distribution.keys().for_each(|collator| {
Self::process_collator_with_rewards(
round_to_payout,
collator.clone(),
collator_reward,
)
});
},
}
}
fn pay_stakers(now: RoundIndex) {
let duration = T::RewardPaymentDelay::get();
if now < duration {
return
}
let round_to_payout = now.saturating_sub(duration);
let total = <Points<T>>::take(round_to_payout);
if total.is_zero() {
return
}
let total_issuance =
T::Issuance::get_staking_issuance(round_to_payout).unwrap_or(Zero::zero());
let round_aggregator_info =
RoundAggregatorInfo::<T>::take(round_to_payout).unwrap_or_default();
for (author, pts) in <AwardedPts<T>>::drain_prefix(round_to_payout) {
let author_issuance_perbill = Perbill::from_rational(pts, total);
let author_rewards = author_issuance_perbill.mul_floor(total_issuance);
match round_aggregator_info.get(&author) {
Some(aggregator_distribution) =>
Self::process_aggregator_with_rewards_and_dist(
round_to_payout,
author,
author_rewards,
aggregator_distribution,
),
None =>
Self::process_collator_with_rewards(round_to_payout, author, author_rewards),
}
}
}
pub fn calculate_collators_valuations<'a, I>(
valuated_bond_it: I,
) -> BTreeMap<T::AccountId, BalanceOf<T>>
where
I: Iterator<
Item = (&'a Bond<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>, BalanceOf<T>),
>,
I: Clone,
{
let aggregator_info = <CandidateAggregator<T>>::get();
let mut valuated_bonds = valuated_bond_it
.clone()
.filter_map(|(bond, valuation)| {
aggregator_info.get(&bond.owner).map(|aggregator| (bond, valuation, aggregator))
})
.fold(
BTreeMap::<T::AccountId, BalanceOf<T>>::new(),
|mut acc, (_bond, valuation, aggregator)| {
acc.entry(aggregator.clone())
.and_modify(|total| *total = total.saturating_add(valuation))
.or_insert_with(|| valuation);
acc
},
);
valuated_bonds.extend(valuated_bond_it.filter_map(|(bond, valuation)| {
if let None = aggregator_info.get(&bond.owner) {
Some((bond.owner.clone(), valuation))
} else {
None
}
}));
valuated_bonds
}
pub fn calculate_aggregators_collator_info<'a, I>(
valuated_bond_it: I,
) -> BTreeMap<T::AccountId, BTreeMap<T::AccountId, BalanceOf<T>>>
where
I: Iterator<
Item = (&'a Bond<T::AccountId, BalanceOf<T>, CurrencyIdOf<T>>, BalanceOf<T>),
>,
{
let aggregator_info = <CandidateAggregator<T>>::get();
valuated_bond_it
.filter_map(|(bond, valuation)| {
aggregator_info.get(&bond.owner).map(|aggregator| (bond, valuation, aggregator))
})
.fold(
BTreeMap::<T::AccountId, BTreeMap<T::AccountId, BalanceOf<T>>>::new(),
|mut acc, (bond, valuation, aggregator)| {
acc.entry(aggregator.clone())
.and_modify(|x| {
x.insert(bond.owner.clone(), valuation);
})
.or_insert(BTreeMap::from([(bond.owner.clone(), valuation)]));
acc
},
)
}
pub fn calculate_valuations_and_aggregation_info() -> (
BTreeMap<T::AccountId, BalanceOf<T>>,
BTreeMap<T::AccountId, BTreeMap<T::AccountId, BalanceOf<T>>>,
) {
let candidates = <CandidatePool<T>>::get().0;
let liq_token_to_pool = <StakingLiquidityTokens<T>>::get();
let valuated_bond_it = candidates.iter().filter_map(|bond| {
if bond.liquidity_token == T::NativeTokenId::get() {
Some((bond, bond.amount.checked_div(&2_u32.into()).unwrap_or_default()))
} else {
match liq_token_to_pool.get(&bond.liquidity_token) {
Some(Some((reserve1, reserve2))) if !reserve1.is_zero() =>
multiply_by_rational_with_rounding(
bond.amount.into(),
(*reserve1).into(),
(*reserve2).into(),
Rounding::Down,
)
.map(SaturatedConversion::saturated_into)
.map(|val| (bond, val))
.or(Some((bond, BalanceOf::<T>::max_value()))),
_ => None,
}
}
});
(
Self::calculate_collators_valuations(valuated_bond_it.clone()),
Self::calculate_aggregators_collator_info(valuated_bond_it.clone()),
)
}
pub fn compute_top_candidates() -> (
Vec<(T::AccountId, BalanceOf<T>)>,
Vec<(T::AccountId, BalanceOf<T>)>,
BTreeMap<T::AccountId, BTreeMap<T::AccountId, BalanceOf<T>>>,
) {
let (valuated_author_candidates_btreemap, aggregators_collator_info) =
Self::calculate_valuations_and_aggregation_info();
let mut valuated_author_candidates_vec: Vec<(T::AccountId, BalanceOf<T>)> =
valuated_author_candidates_btreemap.into_iter().collect::<_>();
valuated_author_candidates_vec.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
let top_n = <TotalSelected<T>>::get() as usize;
let mut selected_authors: Vec<(T::AccountId, BalanceOf<T>)> =
valuated_author_candidates_vec
.into_iter()
.rev()
.take(top_n)
.filter(|x| x.1 >= T::MinCollatorStk::get())
.collect::<_>();
selected_authors.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
let mut all_selected_collators = Vec::<(T::AccountId, BalanceOf<T>)>::new();
for selected_author in selected_authors.iter() {
if let Some(aggregator_collator_info) =
aggregators_collator_info.get(&selected_author.0)
{
all_selected_collators.extend_from_slice(
&{
aggregator_collator_info
.into_iter()
.map(|(a, b)| (a.clone(), b.clone()))
.collect::<Vec<(T::AccountId, BalanceOf<T>)>>()
}[..],
);
} else {
all_selected_collators.push(selected_author.clone());
}
}
(selected_authors, all_selected_collators, aggregators_collator_info)
}
pub fn staking_liquidity_tokens_snapshot() {
let mut staking_liquidity_tokens = <StakingLiquidityTokens<T>>::get();
for (token, valuation) in staking_liquidity_tokens.iter_mut() {
*valuation = T::StakingLiquidityTokenValuator::get_pool_state((*token).into());
}
<StakingLiquidityTokens<T>>::put(staking_liquidity_tokens);
}
#[aquamarine::aquamarine]
pub fn select_top_candidates(now: RoundIndex) -> (u32, u32, BalanceOf<T>) {
let (mut collator_count, mut delegation_count, mut total_relevant_exposure) =
(0u32, 0u32, BalanceOf::<T>::zero());
Self::staking_liquidity_tokens_snapshot();
let (selected_authors, all_selected_collators, aggregators_collator_info) =
Self::compute_top_candidates();
RoundAggregatorInfo::<T>::insert(now, aggregators_collator_info);
for collator in all_selected_collators.iter() {
let state = <CandidateState<T>>::get(&collator.0)
.expect("all members of CandidateQ must be candidates");
collator_count = collator_count.saturating_add(1u32);
delegation_count = delegation_count.saturating_add(state.delegators.0.len() as u32);
let amount = collator.1;
total_relevant_exposure = total_relevant_exposure.saturating_add(amount);
let collator_snaphot: CollatorSnapshot<
T::AccountId,
BalanceOf<T>,
CurrencyIdOf<T>,
> = state.into();
<AtStake<T>>::insert(now, collator.0.clone(), collator_snaphot);
Self::deposit_event(Event::CollatorChosen(now, collator.0.clone(), amount));
}
<SelectedCandidates<T>>::put(
selected_authors.iter().cloned().map(|x| x.0).collect::<Vec<T::AccountId>>(),
);
(collator_count, delegation_count, total_relevant_exposure)
}
fn valuate_bond(liquidity_token: CurrencyIdOf<T>, bond: BalanceOf<T>) -> BalanceOf<T> {
if liquidity_token == T::NativeTokenId::get() {
bond.checked_div(&2_u32.into()).unwrap_or_default()
} else {
T::StakingLiquidityTokenValuator::valuate_liquidity_token(
liquidity_token.into(),
bond,
)
}
}
fn do_payout_collator_rewards(
collator: T::AccountId,
number_of_sesisons: Option<u32>,
) -> DispatchResultWithPostInfo {
let mut rounds = Vec::<RoundIndex>::new();
let limit = number_of_sesisons.unwrap_or(T::DefaultPayoutLimit::get());
let mut payouts_left = limit * (T::MaxDelegationsPerDelegator::get() + 1);
for (id, (round, info)) in
RoundCollatorRewardInfo::<T>::iter_prefix(collator.clone()).enumerate()
{
if payouts_left < (info.delegator_rewards.len() as u32 + 1u32) {
break
}
Self::payout_reward(
round,
collator.clone(),
info.collator_reward,
RewardKind::Collator,
)?;
payouts_left -= 1u32;
let _ = info.delegator_rewards.iter().try_for_each(|(d, r)| {
Self::payout_reward(
round,
d.clone(),
r.clone(),
RewardKind::Delegator(collator.clone()),
)
})?;
RoundCollatorRewardInfo::<T>::remove(collator.clone(), round);
rounds.push(round);
payouts_left = payouts_left
.checked_sub(info.delegator_rewards.len() as u32)
.unwrap_or_default();
if (id as u32).checked_add(1u32).unwrap_or(u32::MAX) > limit {
payouts_left = payouts_left.checked_sub(1).unwrap_or_default();
}
}
ensure!(!rounds.is_empty(), Error::<T>::CollatorRoundRewardsDNE);
if let Some(_) = RoundCollatorRewardInfo::<T>::iter_prefix(collator.clone()).next() {
Self::deposit_event(Event::CollatorRewardsDistributed(
collator,
PayoutRounds::Partial(rounds),
));
} else {
Self::deposit_event(Event::CollatorRewardsDistributed(collator, PayoutRounds::All));
}
Ok(().into())
}
}
impl<T: Config> pallet_authorship::EventHandler<T::AccountId, BlockNumberFor<T>> for Pallet<T> {
fn note_author(author: T::AccountId) {
let now = <Round<T>>::get().current;
let score_plus_20 = <AwardedPts<T>>::get(now, &author).saturating_add(20);
<AwardedPts<T>>::insert(now, author, score_plus_20);
<Points<T>>::mutate(now, |x| *x = x.saturating_add(20));
}
}
impl<T: Config> pallet_session::SessionManager<T::AccountId> for Pallet<T> {
fn new_session(_: SessionIndex) -> Option<Vec<T::AccountId>> {
let selected_canidates = Self::selected_candidates();
if !selected_canidates.is_empty() {
Some(selected_canidates)
} else {
let fallback_canidates = T::FallbackProvider::get_members();
if !fallback_canidates.is_empty() {
Some(fallback_canidates)
} else {
None
}
}
}
fn start_session(session_index: SessionIndex) {
if !session_index.is_zero() {
let n = <frame_system::Pallet<T>>::block_number().saturating_add(One::one());
let mut round = <Round<T>>::get();
round.update(n);
Self::pay_stakers(round.current);
let (collator_count, _delegation_count, total_relevant_exposure) =
Self::select_top_candidates(round.current.saturating_add(One::one()));
T::Issuance::compute_issuance(round.current);
<Round<T>>::put(round);
Self::deposit_event(Event::NewRound(
round.first,
round.current,
collator_count,
total_relevant_exposure,
));
}
}
fn end_session(_: SessionIndex) {
}
}
impl<T: Config> pallet_session::ShouldEndSession<BlockNumberFor<T>> for Pallet<T> {
fn should_end_session(now: BlockNumberFor<T>) -> bool {
let round = <Round<T>>::get();
round.should_update(now)
}
}
impl<T: Config> EstimateNextSessionRotation<BlockNumberFor<T>> for Pallet<T> {
fn average_session_length() -> BlockNumberFor<T> {
<Round<T>>::get().length.into()
}
fn estimate_current_session_progress(now: BlockNumberFor<T>) -> (Option<Permill>, Weight) {
let round = <Round<T>>::get();
let passed_blocks = now.saturating_sub(round.first).saturating_add(One::one());
(
Some(Permill::from_rational(passed_blocks, round.length.into())),
T::DbWeight::get().reads(1),
)
}
fn estimate_next_session_rotation(
_now: BlockNumberFor<T>,
) -> (Option<BlockNumberFor<T>>, Weight) {
let round = <Round<T>>::get();
(
Some(round.first.saturating_add(round.length.saturating_sub(One::one()).into())),
T::DbWeight::get().reads(1),
)
}
}
}