use crate::{
auto_compound::AutoCompoundDelegations, set::OrderedSet, BalanceOf, BottomDelegations,
CandidateInfo, Config, DelegatorState, Error, Event, Pallet, Round, RoundIndex, TopDelegations,
Total, COLLATOR_LOCK_ID, DELEGATOR_LOCK_ID,
};
use frame_support::{
pallet_prelude::*,
traits::{tokens::WithdrawReasons, LockableCurrency},
};
use parity_scale_codec::{Decode, Encode};
use sp_runtime::{
traits::{AtLeast32BitUnsigned, Saturating, Zero},
Perbill, Percent, RuntimeDebug,
};
use sp_std::{cmp::Ordering, collections::btree_map::BTreeMap, prelude::*};
pub struct CountedDelegations<T: Config> {
pub uncounted_stake: BalanceOf<T>,
pub rewardable_delegations: Vec<Bond<T::AccountId, BalanceOf<T>>>,
}
#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct Bond<AccountId, Balance> {
pub owner: AccountId,
pub amount: Balance,
}
impl<A: Decode, B: Default> Default for Bond<A, B> {
fn default() -> Bond<A, B> {
Bond {
owner: A::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes())
.expect("infinite length input; no invalid inputs for type; qed"),
amount: B::default(),
}
}
}
impl<A, B: Default> Bond<A, B> {
pub fn from_owner(owner: A) -> Self {
Bond {
owner,
amount: B::default(),
}
}
}
impl<AccountId: Ord, Balance> Eq for Bond<AccountId, Balance> {}
impl<AccountId: Ord, Balance> Ord for Bond<AccountId, Balance> {
fn cmp(&self, other: &Self) -> Ordering {
self.owner.cmp(&other.owner)
}
}
impl<AccountId: Ord, Balance> PartialOrd for Bond<AccountId, Balance> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<AccountId: Ord, Balance> PartialEq for Bond<AccountId, Balance> {
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(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct BondWithAutoCompound<AccountId, Balance> {
pub owner: AccountId,
pub amount: Balance,
pub auto_compound: Percent,
}
impl<A: Decode, B: Default> Default for BondWithAutoCompound<A, B> {
fn default() -> BondWithAutoCompound<A, B> {
BondWithAutoCompound {
owner: A::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes())
.expect("infinite length input; no invalid inputs for type; qed"),
amount: B::default(),
auto_compound: Percent::zero(),
}
}
}
#[derive(Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct CollatorSnapshot<AccountId, Balance> {
pub bond: Balance,
pub delegations: Vec<BondWithAutoCompound<AccountId, Balance>>,
pub total: Balance,
}
impl<A: PartialEq, B: PartialEq> PartialEq for CollatorSnapshot<A, B> {
fn eq(&self, other: &Self) -> bool {
let must_be_true = self.bond == other.bond && self.total == other.total;
if !must_be_true {
return false;
}
for (
BondWithAutoCompound {
owner: o1,
amount: a1,
auto_compound: c1,
},
BondWithAutoCompound {
owner: o2,
amount: a2,
auto_compound: c2,
},
) in self.delegations.iter().zip(other.delegations.iter())
{
if o1 != o2 || a1 != a2 || c1 != c2 {
return false;
}
}
true
}
}
impl<A, B: Default> Default for CollatorSnapshot<A, B> {
fn default() -> CollatorSnapshot<A, B> {
CollatorSnapshot {
bond: B::default(),
delegations: Vec::new(),
total: B::default(),
}
}
}
#[derive(Clone, Default, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct DelayedPayout<Balance> {
pub round_issuance: Balance,
pub total_staking_reward: Balance,
pub collator_commission: Perbill,
}
#[derive(Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct Collator2<AccountId, Balance> {
pub id: AccountId,
pub bond: Balance,
pub nominators: OrderedSet<AccountId>,
pub top_nominators: Vec<Bond<AccountId, Balance>>,
pub bottom_nominators: Vec<Bond<AccountId, Balance>>,
pub total_counted: Balance,
pub total_backing: Balance,
pub state: CollatorStatus,
}
impl<A, B> From<Collator2<A, B>> for CollatorCandidate<A, B> {
fn from(other: Collator2<A, B>) -> CollatorCandidate<A, B> {
CollatorCandidate {
id: other.id,
bond: other.bond,
delegators: other.nominators,
top_delegations: other.top_nominators,
bottom_delegations: other.bottom_nominators,
total_counted: other.total_counted,
total_backing: other.total_backing,
request: None,
state: other.state,
}
}
}
#[derive(PartialEq, Clone, Copy, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct CandidateBondLessRequest<Balance> {
pub amount: Balance,
pub when_executable: RoundIndex,
}
#[derive(Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct CollatorCandidate<AccountId, Balance> {
pub id: AccountId,
pub bond: Balance,
pub delegators: OrderedSet<AccountId>,
pub top_delegations: Vec<Bond<AccountId, Balance>>,
pub bottom_delegations: Vec<Bond<AccountId, Balance>>,
pub total_counted: Balance,
pub total_backing: Balance,
pub request: Option<CandidateBondLessRequest<Balance>>,
pub state: CollatorStatus,
}
#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct Delegations<AccountId, Balance> {
pub delegations: Vec<Bond<AccountId, Balance>>,
pub total: Balance,
}
impl<A, B: Default> Default for Delegations<A, B> {
fn default() -> Delegations<A, B> {
Delegations {
delegations: Vec::new(),
total: B::default(),
}
}
}
impl<AccountId, Balance: Copy + Ord + sp_std::ops::AddAssign + Zero + Saturating>
Delegations<AccountId, Balance>
{
pub fn sort_greatest_to_least(&mut self) {
self.delegations.sort_by(|a, b| b.amount.cmp(&a.amount));
}
pub fn insert_sorted_greatest_to_least(&mut self, delegation: Bond<AccountId, Balance>) {
self.total = self.total.saturating_add(delegation.amount);
if !self.delegations.is_empty() {
if self.delegations[self.delegations.len() - 1].amount == delegation.amount {
self.delegations.push(delegation);
return;
}
}
match self
.delegations
.binary_search_by(|x| delegation.amount.cmp(&x.amount))
{
Ok(i) => {
let mut new_index = i + 1;
while new_index <= (self.delegations.len() - 1) {
if self.delegations[new_index].amount == delegation.amount {
new_index = new_index.saturating_add(1);
} else {
self.delegations.insert(new_index, delegation);
return;
}
}
self.delegations.push(delegation)
}
Err(i) => self.delegations.insert(i, delegation),
}
}
pub fn top_capacity<T: Config>(&self) -> CapacityStatus {
match &self.delegations {
x if x.len() as u32 >= T::MaxTopDelegationsPerCandidate::get() => CapacityStatus::Full,
x if x.is_empty() => CapacityStatus::Empty,
_ => CapacityStatus::Partial,
}
}
pub fn bottom_capacity<T: Config>(&self) -> CapacityStatus {
match &self.delegations {
x if x.len() as u32 >= T::MaxBottomDelegationsPerCandidate::get() => {
CapacityStatus::Full
}
x if x.is_empty() => CapacityStatus::Empty,
_ => CapacityStatus::Partial,
}
}
pub fn lowest_delegation_amount(&self) -> Balance {
self.delegations
.last()
.map(|x| x.amount)
.unwrap_or(Balance::zero())
}
pub fn highest_delegation_amount(&self) -> Balance {
self.delegations
.first()
.map(|x| x.amount)
.unwrap_or(Balance::zero())
}
}
#[derive(PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub enum CapacityStatus {
Full,
Empty,
Partial,
}
#[derive(Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct CandidateMetadata<Balance> {
pub bond: Balance,
pub delegation_count: u32,
pub total_counted: Balance,
pub lowest_top_delegation_amount: Balance,
pub highest_bottom_delegation_amount: Balance,
pub lowest_bottom_delegation_amount: Balance,
pub top_capacity: CapacityStatus,
pub bottom_capacity: CapacityStatus,
pub request: Option<CandidateBondLessRequest<Balance>>,
pub status: CollatorStatus,
}
impl<
Balance: Copy
+ Zero
+ PartialOrd
+ sp_std::ops::AddAssign
+ sp_std::ops::SubAssign
+ sp_std::ops::Sub<Output = Balance>
+ sp_std::fmt::Debug
+ Saturating,
> CandidateMetadata<Balance>
{
pub fn new(bond: Balance) -> Self {
CandidateMetadata {
bond,
delegation_count: 0u32,
total_counted: bond,
lowest_top_delegation_amount: Zero::zero(),
highest_bottom_delegation_amount: Zero::zero(),
lowest_bottom_delegation_amount: Zero::zero(),
top_capacity: CapacityStatus::Empty,
bottom_capacity: CapacityStatus::Empty,
request: None,
status: CollatorStatus::Active,
}
}
pub fn is_active(&self) -> bool {
matches!(self.status, CollatorStatus::Active)
}
pub fn is_leaving(&self) -> bool {
matches!(self.status, CollatorStatus::Leaving(_))
}
pub fn schedule_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 + T::LeaveCandidatesDelay::get();
self.status = CollatorStatus::Leaving(when);
Ok((now, when))
}
pub fn can_leave<T: Config>(&self) -> DispatchResult {
if let CollatorStatus::Leaving(when) = self.status {
ensure!(
<Round<T>>::get().current >= when,
Error::<T>::CandidateCannotLeaveYet
);
Ok(())
} else {
Err(Error::<T>::CandidateNotLeaving.into())
}
}
pub fn go_offline(&mut self) {
self.status = CollatorStatus::Idle;
}
pub fn go_online(&mut self) {
self.status = CollatorStatus::Active;
}
pub fn bond_more<T: Config>(&mut self, who: T::AccountId, more: Balance) -> DispatchResult
where
BalanceOf<T>: From<Balance>,
{
ensure!(
<Pallet<T>>::get_collator_stakable_free_balance(&who) >= more.into(),
Error::<T>::InsufficientBalance
);
let new_total = <Total<T>>::get().saturating_add(more.into());
<Total<T>>::put(new_total);
self.bond = self.bond.saturating_add(more);
T::Currency::set_lock(
COLLATOR_LOCK_ID,
&who.clone(),
self.bond.into(),
WithdrawReasons::all(),
);
self.total_counted = self.total_counted.saturating_add(more);
<Pallet<T>>::deposit_event(Event::CandidateBondedMore {
candidate: who.clone(),
amount: more.into(),
new_total_bond: self.bond.into(),
});
Ok(())
}
pub fn bond_less<T: Config>(&mut self, who: T::AccountId, amount: Balance)
where
BalanceOf<T>: From<Balance>,
{
let new_total_staked = <Total<T>>::get().saturating_sub(amount.into());
<Total<T>>::put(new_total_staked);
self.bond = self.bond.saturating_sub(amount);
if self.bond.is_zero() {
T::Currency::remove_lock(COLLATOR_LOCK_ID, &who);
} else {
T::Currency::set_lock(
COLLATOR_LOCK_ID,
&who,
self.bond.into(),
WithdrawReasons::all(),
);
}
self.total_counted = self.total_counted.saturating_sub(amount);
let event = Event::CandidateBondedLess {
candidate: who.clone(),
amount: amount.into(),
new_bond: self.bond.into(),
};
if self.is_active() {
Pallet::<T>::update_active(who, self.total_counted.into());
}
Pallet::<T>::deposit_event(event);
}
pub fn schedule_bond_less<T: Config>(
&mut self,
less: Balance,
) -> Result<RoundIndex, DispatchError>
where
BalanceOf<T>: Into<Balance>,
{
ensure!(
self.request.is_none(),
Error::<T>::PendingCandidateRequestAlreadyExists
);
ensure!(self.bond > less, Error::<T>::CandidateBondBelowMin);
ensure!(
self.bond - less >= T::MinCandidateStk::get().into(),
Error::<T>::CandidateBondBelowMin
);
let when_executable = <Round<T>>::get().current + T::CandidateBondLessDelay::get();
self.request = Some(CandidateBondLessRequest {
amount: less,
when_executable,
});
Ok(when_executable)
}
pub fn execute_bond_less<T: Config>(&mut self, who: T::AccountId) -> DispatchResult
where
BalanceOf<T>: From<Balance>,
{
let request = self
.request
.ok_or(Error::<T>::PendingCandidateRequestsDNE)?;
ensure!(
request.when_executable <= <Round<T>>::get().current,
Error::<T>::PendingCandidateRequestNotDueYet
);
self.bond_less::<T>(who.clone(), request.amount);
self.request = None;
Ok(())
}
pub fn cancel_bond_less<T: Config>(&mut self, who: T::AccountId) -> DispatchResult
where
BalanceOf<T>: From<Balance>,
{
let request = self
.request
.ok_or(Error::<T>::PendingCandidateRequestsDNE)?;
let event = Event::CancelledCandidateBondLess {
candidate: who.clone().into(),
amount: request.amount.into(),
execute_round: request.when_executable,
};
self.request = None;
Pallet::<T>::deposit_event(event);
Ok(())
}
pub fn reset_top_data<T: Config>(
&mut self,
candidate: T::AccountId,
top_delegations: &Delegations<T::AccountId, BalanceOf<T>>,
) where
BalanceOf<T>: Into<Balance> + From<Balance>,
{
self.lowest_top_delegation_amount = top_delegations.lowest_delegation_amount().into();
self.top_capacity = top_delegations.top_capacity::<T>();
let old_total_counted = self.total_counted;
self.total_counted = self.bond.saturating_add(top_delegations.total.into());
if old_total_counted != self.total_counted && self.is_active() {
Pallet::<T>::update_active(candidate, self.total_counted.into());
}
}
pub fn reset_bottom_data<T: Config>(
&mut self,
bottom_delegations: &Delegations<T::AccountId, BalanceOf<T>>,
) where
BalanceOf<T>: Into<Balance>,
{
self.lowest_bottom_delegation_amount = bottom_delegations.lowest_delegation_amount().into();
self.highest_bottom_delegation_amount =
bottom_delegations.highest_delegation_amount().into();
self.bottom_capacity = bottom_delegations.bottom_capacity::<T>();
}
pub fn add_delegation<T: Config>(
&mut self,
candidate: &T::AccountId,
delegation: Bond<T::AccountId, BalanceOf<T>>,
) -> Result<(DelegatorAdded<Balance>, Option<Balance>), DispatchError>
where
BalanceOf<T>: Into<Balance> + From<Balance>,
{
let mut less_total_staked = None;
let delegator_added = match self.top_capacity {
CapacityStatus::Full => {
if self.lowest_top_delegation_amount < delegation.amount.into() {
less_total_staked = self.add_top_delegation::<T>(candidate, delegation);
DelegatorAdded::AddedToTop {
new_total: self.total_counted,
}
} else {
if matches!(self.bottom_capacity, CapacityStatus::Full) {
ensure!(
delegation.amount.into() > self.lowest_bottom_delegation_amount,
Error::<T>::CannotDelegateLessThanOrEqualToLowestBottomWhenFull
);
less_total_staked = Some(self.lowest_bottom_delegation_amount);
}
self.add_bottom_delegation::<T>(false, candidate, delegation);
DelegatorAdded::AddedToBottom
}
}
_ => {
self.add_top_delegation::<T>(candidate, delegation);
DelegatorAdded::AddedToTop {
new_total: self.total_counted,
}
}
};
Ok((delegator_added, less_total_staked))
}
pub fn add_top_delegation<T: Config>(
&mut self,
candidate: &T::AccountId,
delegation: Bond<T::AccountId, BalanceOf<T>>,
) -> Option<Balance>
where
BalanceOf<T>: Into<Balance> + From<Balance>,
{
let mut less_total_staked = None;
let mut top_delegations = <TopDelegations<T>>::get(candidate)
.expect("CandidateInfo existence => TopDelegations existence");
let max_top_delegations_per_candidate = T::MaxTopDelegationsPerCandidate::get();
if top_delegations.delegations.len() as u32 == max_top_delegations_per_candidate {
let new_bottom_delegation = top_delegations.delegations.pop().expect("");
top_delegations.total = top_delegations
.total
.saturating_sub(new_bottom_delegation.amount);
if matches!(self.bottom_capacity, CapacityStatus::Full) {
less_total_staked = Some(self.lowest_bottom_delegation_amount);
}
self.add_bottom_delegation::<T>(true, candidate, new_bottom_delegation);
}
top_delegations.insert_sorted_greatest_to_least(delegation);
self.reset_top_data::<T>(candidate.clone(), &top_delegations);
if less_total_staked.is_none() {
self.delegation_count = self.delegation_count.saturating_add(1u32);
}
<TopDelegations<T>>::insert(&candidate, top_delegations);
less_total_staked
}
pub fn add_bottom_delegation<T: Config>(
&mut self,
bumped_from_top: bool,
candidate: &T::AccountId,
delegation: Bond<T::AccountId, BalanceOf<T>>,
) where
BalanceOf<T>: Into<Balance> + From<Balance>,
{
let mut bottom_delegations = <BottomDelegations<T>>::get(candidate)
.expect("CandidateInfo existence => BottomDelegations existence");
let increase_delegation_count = if bottom_delegations.delegations.len() as u32
== T::MaxBottomDelegationsPerCandidate::get()
{
let lowest_bottom_to_be_kicked = bottom_delegations
.delegations
.pop()
.expect("if at full capacity (>0), then >0 bottom delegations exist; qed");
bottom_delegations.total = bottom_delegations
.total
.saturating_sub(lowest_bottom_to_be_kicked.amount);
let mut delegator_state = <DelegatorState<T>>::get(&lowest_bottom_to_be_kicked.owner)
.expect("Delegation existence => DelegatorState existence");
let leaving = delegator_state.delegations.0.len() == 1usize;
delegator_state.rm_delegation::<T>(candidate);
<Pallet<T>>::delegation_remove_request_with_state(
&candidate,
&lowest_bottom_to_be_kicked.owner,
&mut delegator_state,
);
<AutoCompoundDelegations<T>>::remove_auto_compound(
&candidate,
&lowest_bottom_to_be_kicked.owner,
);
Pallet::<T>::deposit_event(Event::DelegationKicked {
delegator: lowest_bottom_to_be_kicked.owner.clone(),
candidate: candidate.clone(),
unstaked_amount: lowest_bottom_to_be_kicked.amount,
});
if leaving {
<DelegatorState<T>>::remove(&lowest_bottom_to_be_kicked.owner);
Pallet::<T>::deposit_event(Event::DelegatorLeft {
delegator: lowest_bottom_to_be_kicked.owner,
unstaked_amount: lowest_bottom_to_be_kicked.amount,
});
} else {
<DelegatorState<T>>::insert(&lowest_bottom_to_be_kicked.owner, delegator_state);
}
false
} else {
!bumped_from_top
};
if increase_delegation_count {
self.delegation_count = self.delegation_count.saturating_add(1u32);
}
bottom_delegations.insert_sorted_greatest_to_least(delegation);
self.reset_bottom_data::<T>(&bottom_delegations);
<BottomDelegations<T>>::insert(candidate, bottom_delegations);
}
pub fn rm_delegation_if_exists<T: Config>(
&mut self,
candidate: &T::AccountId,
delegator: T::AccountId,
amount: Balance,
) -> Result<bool, DispatchError>
where
BalanceOf<T>: Into<Balance> + From<Balance>,
{
let amount_geq_lowest_top = amount >= self.lowest_top_delegation_amount;
let top_is_not_full = !matches!(self.top_capacity, CapacityStatus::Full);
let lowest_top_eq_highest_bottom =
self.lowest_top_delegation_amount == self.highest_bottom_delegation_amount;
let delegation_dne_err: DispatchError = Error::<T>::DelegationDNE.into();
if top_is_not_full || (amount_geq_lowest_top && !lowest_top_eq_highest_bottom) {
self.rm_top_delegation::<T>(candidate, delegator)
} else if amount_geq_lowest_top && lowest_top_eq_highest_bottom {
let result = self.rm_top_delegation::<T>(candidate, delegator.clone());
if result == Err(delegation_dne_err) {
self.rm_bottom_delegation::<T>(candidate, delegator)
} else {
result
}
} else {
self.rm_bottom_delegation::<T>(candidate, delegator)
}
}
pub fn rm_top_delegation<T: Config>(
&mut self,
candidate: &T::AccountId,
delegator: T::AccountId,
) -> Result<bool, DispatchError>
where
BalanceOf<T>: Into<Balance> + From<Balance>,
{
let old_total_counted = self.total_counted;
let mut top_delegations = <TopDelegations<T>>::get(candidate)
.expect("CandidateInfo exists => TopDelegations exists");
let mut actual_amount_option: Option<BalanceOf<T>> = None;
top_delegations.delegations = top_delegations
.delegations
.clone()
.into_iter()
.filter(|d| {
if d.owner != delegator {
true
} else {
actual_amount_option = Some(d.amount);
false
}
})
.collect();
let actual_amount = actual_amount_option.ok_or(Error::<T>::DelegationDNE)?;
top_delegations.total = top_delegations.total.saturating_sub(actual_amount);
if !matches!(self.bottom_capacity, CapacityStatus::Empty) {
let mut bottom_delegations =
<BottomDelegations<T>>::get(candidate).expect("bottom is nonempty as just checked");
let highest_bottom_delegation = bottom_delegations.delegations.remove(0);
bottom_delegations.total = bottom_delegations
.total
.saturating_sub(highest_bottom_delegation.amount);
self.reset_bottom_data::<T>(&bottom_delegations);
<BottomDelegations<T>>::insert(candidate, bottom_delegations);
top_delegations.insert_sorted_greatest_to_least(highest_bottom_delegation);
}
self.reset_top_data::<T>(candidate.clone(), &top_delegations);
self.delegation_count = self.delegation_count.saturating_sub(1u32);
<TopDelegations<T>>::insert(candidate, top_delegations);
Ok(old_total_counted == self.total_counted)
}
pub fn rm_bottom_delegation<T: Config>(
&mut self,
candidate: &T::AccountId,
delegator: T::AccountId,
) -> Result<bool, DispatchError>
where
BalanceOf<T>: Into<Balance>,
{
let mut bottom_delegations = <BottomDelegations<T>>::get(candidate)
.expect("CandidateInfo exists => BottomDelegations exists");
let mut actual_amount_option: Option<BalanceOf<T>> = None;
bottom_delegations.delegations = bottom_delegations
.delegations
.clone()
.into_iter()
.filter(|d| {
if d.owner != delegator {
true
} else {
actual_amount_option = Some(d.amount);
false
}
})
.collect();
let actual_amount = actual_amount_option.ok_or(Error::<T>::DelegationDNE)?;
bottom_delegations.total = bottom_delegations.total.saturating_sub(actual_amount);
self.reset_bottom_data::<T>(&bottom_delegations);
self.delegation_count = self.delegation_count.saturating_sub(1u32);
<BottomDelegations<T>>::insert(candidate, bottom_delegations);
Ok(false)
}
pub fn increase_delegation<T: Config>(
&mut self,
candidate: &T::AccountId,
delegator: T::AccountId,
bond: BalanceOf<T>,
more: BalanceOf<T>,
) -> Result<bool, DispatchError>
where
BalanceOf<T>: Into<Balance> + From<Balance>,
{
let lowest_top_eq_highest_bottom =
self.lowest_top_delegation_amount == self.highest_bottom_delegation_amount;
let bond_geq_lowest_top = bond.into() >= self.lowest_top_delegation_amount;
let delegation_dne_err: DispatchError = Error::<T>::DelegationDNE.into();
if bond_geq_lowest_top && !lowest_top_eq_highest_bottom {
self.increase_top_delegation::<T>(candidate, delegator.clone(), more)
} else if bond_geq_lowest_top && lowest_top_eq_highest_bottom {
let result = self.increase_top_delegation::<T>(candidate, delegator.clone(), more);
if result == Err(delegation_dne_err) {
self.increase_bottom_delegation::<T>(candidate, delegator, bond, more)
} else {
result
}
} else {
self.increase_bottom_delegation::<T>(candidate, delegator, bond, more)
}
}
pub fn increase_top_delegation<T: Config>(
&mut self,
candidate: &T::AccountId,
delegator: T::AccountId,
more: BalanceOf<T>,
) -> Result<bool, DispatchError>
where
BalanceOf<T>: Into<Balance> + From<Balance>,
{
let mut top_delegations = <TopDelegations<T>>::get(candidate)
.expect("CandidateInfo exists => TopDelegations exists");
let mut in_top = false;
top_delegations.delegations = top_delegations
.delegations
.clone()
.into_iter()
.map(|d| {
if d.owner != delegator {
d
} else {
in_top = true;
let new_amount = d.amount.saturating_add(more);
Bond {
owner: d.owner,
amount: new_amount,
}
}
})
.collect();
ensure!(in_top, Error::<T>::DelegationDNE);
top_delegations.total = top_delegations.total.saturating_add(more);
top_delegations.sort_greatest_to_least();
self.reset_top_data::<T>(candidate.clone(), &top_delegations);
<TopDelegations<T>>::insert(candidate, top_delegations);
Ok(true)
}
pub fn increase_bottom_delegation<T: Config>(
&mut self,
candidate: &T::AccountId,
delegator: T::AccountId,
bond: BalanceOf<T>,
more: BalanceOf<T>,
) -> Result<bool, DispatchError>
where
BalanceOf<T>: Into<Balance> + From<Balance>,
{
let mut bottom_delegations =
<BottomDelegations<T>>::get(candidate).ok_or(Error::<T>::CandidateDNE)?;
let mut delegation_option: Option<Bond<T::AccountId, BalanceOf<T>>> = None;
let in_top_after = if (bond.saturating_add(more)).into() > self.lowest_top_delegation_amount
{
bottom_delegations.delegations = bottom_delegations
.delegations
.clone()
.into_iter()
.filter(|d| {
if d.owner != delegator {
true
} else {
delegation_option = Some(Bond {
owner: d.owner.clone(),
amount: d.amount.saturating_add(more),
});
false
}
})
.collect();
let delegation = delegation_option.ok_or(Error::<T>::DelegationDNE)?;
bottom_delegations.total = bottom_delegations.total.saturating_sub(bond);
let mut top_delegations = <TopDelegations<T>>::get(candidate)
.expect("CandidateInfo existence => TopDelegations existence");
if matches!(top_delegations.top_capacity::<T>(), CapacityStatus::Full) {
let new_bottom_delegation = top_delegations
.delegations
.pop()
.expect("Top capacity full => Exists at least 1 top delegation");
top_delegations.total = top_delegations
.total
.saturating_sub(new_bottom_delegation.amount);
bottom_delegations.insert_sorted_greatest_to_least(new_bottom_delegation);
}
top_delegations.insert_sorted_greatest_to_least(delegation);
self.reset_top_data::<T>(candidate.clone(), &top_delegations);
<TopDelegations<T>>::insert(candidate, top_delegations);
true
} else {
let mut in_bottom = false;
bottom_delegations.delegations = bottom_delegations
.delegations
.clone()
.into_iter()
.map(|d| {
if d.owner != delegator {
d
} else {
in_bottom = true;
Bond {
owner: d.owner,
amount: d.amount.saturating_add(more),
}
}
})
.collect();
ensure!(in_bottom, Error::<T>::DelegationDNE);
bottom_delegations.total = bottom_delegations.total.saturating_add(more);
bottom_delegations.sort_greatest_to_least();
false
};
self.reset_bottom_data::<T>(&bottom_delegations);
<BottomDelegations<T>>::insert(candidate, bottom_delegations);
Ok(in_top_after)
}
pub fn decrease_delegation<T: Config>(
&mut self,
candidate: &T::AccountId,
delegator: T::AccountId,
bond: Balance,
less: BalanceOf<T>,
) -> Result<bool, DispatchError>
where
BalanceOf<T>: Into<Balance> + From<Balance>,
{
let lowest_top_eq_highest_bottom =
self.lowest_top_delegation_amount == self.highest_bottom_delegation_amount;
let bond_geq_lowest_top = bond >= self.lowest_top_delegation_amount;
let delegation_dne_err: DispatchError = Error::<T>::DelegationDNE.into();
if bond_geq_lowest_top && !lowest_top_eq_highest_bottom {
self.decrease_top_delegation::<T>(candidate, delegator.clone(), bond.into(), less)
} else if bond_geq_lowest_top && lowest_top_eq_highest_bottom {
let result =
self.decrease_top_delegation::<T>(candidate, delegator.clone(), bond.into(), less);
if result == Err(delegation_dne_err) {
self.decrease_bottom_delegation::<T>(candidate, delegator, less)
} else {
result
}
} else {
self.decrease_bottom_delegation::<T>(candidate, delegator, less)
}
}
pub fn decrease_top_delegation<T: Config>(
&mut self,
candidate: &T::AccountId,
delegator: T::AccountId,
bond: BalanceOf<T>,
less: BalanceOf<T>,
) -> Result<bool, DispatchError>
where
BalanceOf<T>: Into<Balance> + From<Balance>,
{
let bond_after_less_than_highest_bottom =
bond.saturating_sub(less).into() < self.highest_bottom_delegation_amount;
let full_top_and_nonempty_bottom = matches!(self.top_capacity, CapacityStatus::Full)
&& !matches!(self.bottom_capacity, CapacityStatus::Empty);
let mut top_delegations =
<TopDelegations<T>>::get(candidate).ok_or(Error::<T>::CandidateDNE)?;
let in_top_after = if bond_after_less_than_highest_bottom && full_top_and_nonempty_bottom {
let mut delegation_option: Option<Bond<T::AccountId, BalanceOf<T>>> = None;
top_delegations.delegations = top_delegations
.delegations
.clone()
.into_iter()
.filter(|d| {
if d.owner != delegator {
true
} else {
top_delegations.total = top_delegations.total.saturating_sub(d.amount);
delegation_option = Some(Bond {
owner: d.owner.clone(),
amount: d.amount.saturating_sub(less),
});
false
}
})
.collect();
let delegation = delegation_option.ok_or(Error::<T>::DelegationDNE)?;
let mut bottom_delegations = <BottomDelegations<T>>::get(candidate)
.expect("CandidateInfo existence => BottomDelegations existence");
let highest_bottom_delegation = bottom_delegations.delegations.remove(0);
bottom_delegations.total = bottom_delegations
.total
.saturating_sub(highest_bottom_delegation.amount);
top_delegations.insert_sorted_greatest_to_least(highest_bottom_delegation);
bottom_delegations.insert_sorted_greatest_to_least(delegation);
self.reset_bottom_data::<T>(&bottom_delegations);
<BottomDelegations<T>>::insert(candidate, bottom_delegations);
false
} else {
let mut is_in_top = false;
top_delegations.delegations = top_delegations
.delegations
.clone()
.into_iter()
.map(|d| {
if d.owner != delegator {
d
} else {
is_in_top = true;
Bond {
owner: d.owner,
amount: d.amount.saturating_sub(less),
}
}
})
.collect();
ensure!(is_in_top, Error::<T>::DelegationDNE);
top_delegations.total = top_delegations.total.saturating_sub(less);
top_delegations.sort_greatest_to_least();
true
};
self.reset_top_data::<T>(candidate.clone(), &top_delegations);
<TopDelegations<T>>::insert(candidate, top_delegations);
Ok(in_top_after)
}
pub fn decrease_bottom_delegation<T: Config>(
&mut self,
candidate: &T::AccountId,
delegator: T::AccountId,
less: BalanceOf<T>,
) -> Result<bool, DispatchError>
where
BalanceOf<T>: Into<Balance>,
{
let mut bottom_delegations = <BottomDelegations<T>>::get(candidate)
.expect("CandidateInfo exists => BottomDelegations exists");
let mut in_bottom = false;
bottom_delegations.delegations = bottom_delegations
.delegations
.clone()
.into_iter()
.map(|d| {
if d.owner != delegator {
d
} else {
in_bottom = true;
Bond {
owner: d.owner,
amount: d.amount.saturating_sub(less),
}
}
})
.collect();
ensure!(in_bottom, Error::<T>::DelegationDNE);
bottom_delegations.sort_greatest_to_least();
self.reset_bottom_data::<T>(&bottom_delegations);
<BottomDelegations<T>>::insert(candidate, bottom_delegations);
Ok(false)
}
}
impl<A: PartialEq, B: PartialEq> PartialEq for CollatorCandidate<A, B> {
fn eq(&self, other: &Self) -> bool {
let must_be_true = self.id == other.id
&& self.bond == other.bond
&& self.total_counted == other.total_counted
&& self.total_backing == other.total_backing
&& self.request == other.request
&& self.state == other.state;
if !must_be_true {
return false;
}
for (x, y) in self.delegators.0.iter().zip(other.delegators.0.iter()) {
if x != y {
return false;
}
}
for (
Bond {
owner: o1,
amount: a1,
},
Bond {
owner: o2,
amount: a2,
},
) in self
.top_delegations
.iter()
.zip(other.top_delegations.iter())
{
if o1 != o2 || a1 != a2 {
return false;
}
}
for (
Bond {
owner: o1,
amount: a1,
},
Bond {
owner: o2,
amount: a2,
},
) in self
.bottom_delegations
.iter()
.zip(other.bottom_delegations.iter())
{
if o1 != o2 || a1 != a2 {
return false;
}
}
true
}
}
#[derive(Clone, Copy, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub enum DelegatorAdded<B> {
AddedToTop { new_total: B },
AddedToBottom,
}
impl<
A: Ord + Clone + sp_std::fmt::Debug,
B: AtLeast32BitUnsigned
+ Ord
+ Copy
+ sp_std::ops::AddAssign
+ sp_std::ops::SubAssign
+ sp_std::fmt::Debug,
> CollatorCandidate<A, B>
{
pub fn is_active(&self) -> bool {
self.state == CollatorStatus::Active
}
}
impl<A: Clone, B: Copy> From<CollatorCandidate<A, B>> for CollatorSnapshot<A, B> {
fn from(other: CollatorCandidate<A, B>) -> CollatorSnapshot<A, B> {
CollatorSnapshot {
bond: other.bond,
delegations: other
.top_delegations
.into_iter()
.map(|d| BondWithAutoCompound {
owner: d.owner,
amount: d.amount,
auto_compound: Percent::zero(),
})
.collect(),
total: other.total_counted,
}
}
}
#[allow(deprecated)]
#[derive(Clone, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub enum DelegatorStatus {
Active,
#[deprecated(note = "must only be used for backwards compatibility reasons")]
Leaving(RoundIndex),
}
#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct Delegator<AccountId, Balance> {
pub id: AccountId,
pub delegations: OrderedSet<Bond<AccountId, Balance>>,
pub total: Balance,
pub less_total: Balance,
pub status: DelegatorStatus,
}
impl<A: PartialEq, B: PartialEq> PartialEq for Delegator<A, B> {
fn eq(&self, other: &Self) -> bool {
let must_be_true = self.id == other.id
&& self.total == other.total
&& self.less_total == other.less_total
&& self.status == other.status;
if !must_be_true {
return false;
}
for (
Bond {
owner: o1,
amount: a1,
},
Bond {
owner: o2,
amount: a2,
},
) in self.delegations.0.iter().zip(other.delegations.0.iter())
{
if o1 != o2 || a1 != a2 {
return false;
}
}
true
}
}
impl<
AccountId: Ord + Clone,
Balance: Copy
+ sp_std::ops::AddAssign
+ sp_std::ops::Add<Output = Balance>
+ sp_std::ops::SubAssign
+ sp_std::ops::Sub<Output = Balance>
+ Ord
+ Zero
+ Default
+ Saturating,
> Delegator<AccountId, Balance>
{
pub fn new(id: AccountId, collator: AccountId, amount: Balance) -> Self {
Delegator {
id,
delegations: OrderedSet::from(vec![Bond {
owner: collator,
amount,
}]),
total: amount,
less_total: Balance::zero(),
status: DelegatorStatus::Active,
}
}
pub fn default_with_total(id: AccountId, amount: Balance) -> Self {
Delegator {
id,
total: amount,
delegations: OrderedSet::from(vec![]),
less_total: Balance::zero(),
status: DelegatorStatus::Active,
}
}
pub fn total(&self) -> Balance {
self.total
}
pub fn total_sub_if<T, F>(&mut self, amount: Balance, check: F) -> DispatchResult
where
T: Config,
T::AccountId: From<AccountId>,
BalanceOf<T>: From<Balance>,
F: Fn(Balance) -> DispatchResult,
{
let total = self.total.saturating_sub(amount);
check(total)?;
self.total = total;
self.adjust_bond_lock::<T>(BondAdjust::Decrease)?;
Ok(())
}
pub fn total_add<T, F>(&mut self, amount: Balance) -> DispatchResult
where
T: Config,
T::AccountId: From<AccountId>,
BalanceOf<T>: From<Balance>,
{
self.total = self.total.saturating_add(amount);
self.adjust_bond_lock::<T>(BondAdjust::Increase(amount))?;
Ok(())
}
pub fn total_sub<T>(&mut self, amount: Balance) -> DispatchResult
where
T: Config,
T::AccountId: From<AccountId>,
BalanceOf<T>: From<Balance>,
{
self.total = self.total.saturating_sub(amount);
self.adjust_bond_lock::<T>(BondAdjust::Decrease)?;
Ok(())
}
pub fn is_active(&self) -> bool {
matches!(self.status, DelegatorStatus::Active)
}
pub fn add_delegation(&mut self, bond: Bond<AccountId, Balance>) -> bool {
let amt = bond.amount;
if self.delegations.insert(bond) {
self.total = self.total.saturating_add(amt);
true
} else {
false
}
}
pub fn rm_delegation<T: Config>(&mut self, collator: &AccountId) -> Option<Balance>
where
BalanceOf<T>: From<Balance>,
T::AccountId: From<AccountId>,
{
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(balance) = amt {
self.delegations = OrderedSet::from(delegations);
self.total_sub::<T>(balance)
.expect("Decreasing lock cannot fail, qed");
Some(self.total)
} else {
None
}
}
pub fn increase_delegation<T: Config>(
&mut self,
candidate: AccountId,
amount: Balance,
) -> Result<bool, sp_runtime::DispatchError>
where
BalanceOf<T>: From<Balance>,
T::AccountId: From<AccountId>,
Delegator<T::AccountId, BalanceOf<T>>: From<Delegator<AccountId, Balance>>,
{
let delegator_id: T::AccountId = self.id.clone().into();
let candidate_id: T::AccountId = candidate.clone().into();
let balance_amt: BalanceOf<T> = amount.into();
for x in &mut self.delegations.0 {
if x.owner == candidate {
let before_amount: BalanceOf<T> = x.amount.into();
x.amount = x.amount.saturating_add(amount);
self.total = self.total.saturating_add(amount);
self.adjust_bond_lock::<T>(BondAdjust::Increase(amount))?;
let mut collator_state =
<CandidateInfo<T>>::get(&candidate_id).ok_or(Error::<T>::CandidateDNE)?;
let before = collator_state.total_counted;
let in_top = collator_state.increase_delegation::<T>(
&candidate_id,
delegator_id.clone(),
before_amount,
balance_amt,
)?;
let after = collator_state.total_counted;
if collator_state.is_active() && (before != after) {
Pallet::<T>::update_active(candidate_id.clone(), after);
}
<CandidateInfo<T>>::insert(&candidate_id, collator_state);
let new_total_staked = <Total<T>>::get().saturating_add(balance_amt);
<Total<T>>::put(new_total_staked);
let nom_st: Delegator<T::AccountId, BalanceOf<T>> = self.clone().into();
<DelegatorState<T>>::insert(&delegator_id, nom_st);
return Ok(in_top);
}
}
Err(Error::<T>::DelegationDNE.into())
}
pub fn adjust_bond_lock<T: Config>(
&mut self,
additional_required_balance: BondAdjust<Balance>,
) -> DispatchResult
where
BalanceOf<T>: From<Balance>,
T::AccountId: From<AccountId>,
{
match additional_required_balance {
BondAdjust::Increase(amount) => {
ensure!(
<Pallet<T>>::get_delegator_stakable_balance(&self.id.clone().into())
>= amount.into(),
Error::<T>::InsufficientBalance,
);
if amount > self.total {
log::warn!("LOGIC ERROR: request to reserve more than bond total");
return Err(DispatchError::Other("Invalid additional_required_balance"));
}
}
BondAdjust::Decrease => (), };
if self.total.is_zero() {
T::Currency::remove_lock(DELEGATOR_LOCK_ID, &self.id.clone().into());
} else {
T::Currency::set_lock(
DELEGATOR_LOCK_ID,
&self.id.clone().into(),
self.total.into(),
WithdrawReasons::all(),
);
}
Ok(())
}
pub fn get_bond_amount(&self, collator: &AccountId) -> Option<Balance> {
self.delegations
.0
.iter()
.find(|b| &b.owner == collator)
.map(|b| b.amount)
}
}
pub mod deprecated {
#![allow(deprecated)]
use super::*;
#[deprecated(note = "use DelegationAction")]
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub enum DelegationChange {
Revoke,
Decrease,
}
#[deprecated(note = "use ScheduledRequest")]
#[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,
}
#[deprecated(note = "use DelegationScheduledRequests storage item")]
#[derive(Clone, Encode, PartialEq, Decode, RuntimeDebug, TypeInfo)]
pub struct PendingDelegationRequests<AccountId, Balance> {
pub revocations_count: u32,
pub requests: BTreeMap<AccountId, DelegationRequest<AccountId, Balance>>,
pub less_total: Balance,
}
impl<A: Ord, B: Zero> Default for PendingDelegationRequests<A, B> {
fn default() -> PendingDelegationRequests<A, B> {
PendingDelegationRequests {
revocations_count: 0u32,
requests: BTreeMap::new(),
less_total: B::zero(),
}
}
}
impl<
A: Ord + Clone,
B: Zero
+ Ord
+ Copy
+ Clone
+ sp_std::ops::AddAssign
+ sp_std::ops::Add<Output = B>
+ sp_std::ops::SubAssign
+ sp_std::ops::Sub<Output = B>
+ Saturating,
> PendingDelegationRequests<A, B>
{
pub fn new() -> Self {
Self::default()
}
}
#[deprecated(note = "use new crate::types::Delegator struct")]
#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct Delegator<AccountId, Balance> {
pub id: AccountId,
pub delegations: OrderedSet<Bond<AccountId, Balance>>,
pub total: Balance,
pub requests: PendingDelegationRequests<AccountId, Balance>,
pub status: DelegatorStatus,
}
#[deprecated(note = "use CollatorSnapshot with BondWithAutoCompound delegations")]
#[derive(Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct CollatorSnapshot<AccountId, Balance> {
pub bond: Balance,
pub delegations: Vec<Bond<AccountId, Balance>>,
pub total: Balance,
}
impl<A: PartialEq, B: PartialEq> PartialEq for CollatorSnapshot<A, B> {
fn eq(&self, other: &Self) -> bool {
let must_be_true = self.bond == other.bond && self.total == other.total;
if !must_be_true {
return false;
}
for (
Bond {
owner: o1,
amount: a1,
},
Bond {
owner: o2,
amount: a2,
},
) in self.delegations.iter().zip(other.delegations.iter())
{
if o1 != o2 || a1 != a2 {
return false;
}
}
true
}
}
impl<A, B: Default> Default for CollatorSnapshot<A, B> {
fn default() -> CollatorSnapshot<A, B> {
CollatorSnapshot {
bond: B::default(),
delegations: Vec::new(),
total: B::default(),
}
}
}
}
#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct Nominator2<AccountId, Balance> {
pub delegations: OrderedSet<Bond<AccountId, Balance>>,
pub revocations: OrderedSet<AccountId>,
pub total: Balance,
pub scheduled_revocations_count: u32,
pub scheduled_revocations_total: Balance,
pub status: DelegatorStatus,
}
#[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct RoundInfo<BlockNumber> {
pub current: RoundIndex,
pub first: BlockNumber,
pub length: u32,
pub first_slot: u64,
}
impl<
B: Copy + sp_std::ops::Add<Output = B> + sp_std::ops::Sub<Output = B> + From<u32> + PartialOrd,
> RoundInfo<B>
{
pub fn new(current: RoundIndex, first: B, length: u32, first_slot: u64) -> RoundInfo<B> {
RoundInfo {
current,
first,
length,
first_slot,
}
}
pub fn should_update(&self, now: B) -> bool {
now - self.first >= self.length.into()
}
pub fn update(&mut self, now: B, now_slot: u64) {
self.current = self.current.saturating_add(1u32);
self.first = now;
self.first_slot = now_slot;
}
}
impl<
B: Copy + sp_std::ops::Add<Output = B> + sp_std::ops::Sub<Output = B> + From<u32> + PartialOrd,
> Default for RoundInfo<B>
{
fn default() -> RoundInfo<B> {
RoundInfo::new(1u32, 1u32.into(), 20u32, 0)
}
}
#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct InflationDistributionConfig<AccountId>(
pub(crate) [InflationDistributionAccount<AccountId>; 2],
);
impl<AccountId> From<[InflationDistributionAccount<AccountId>; 2]>
for InflationDistributionConfig<AccountId>
{
fn from(configs: [InflationDistributionAccount<AccountId>; 2]) -> Self {
InflationDistributionConfig(configs)
}
}
impl<AccountId: Decode> Default for InflationDistributionConfig<AccountId> {
fn default() -> InflationDistributionConfig<AccountId> {
InflationDistributionConfig([
InflationDistributionAccount {
account: AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes())
.expect("infinite length input; no invalid inputs for type; qed"),
percent: Percent::zero(),
},
InflationDistributionAccount {
account: AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes())
.expect("infinite length input; no invalid inputs for type; qed"),
percent: Percent::zero(),
},
])
}
}
#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct InflationDistributionAccount<AccountId> {
pub account: AccountId,
pub percent: Percent,
}
impl<A: Decode> Default for InflationDistributionAccount<A> {
fn default() -> InflationDistributionAccount<A> {
InflationDistributionAccount {
account: A::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes())
.expect("infinite length input; no invalid inputs for type; qed"),
percent: Percent::zero(),
}
}
}
pub enum BondAdjust<Balance> {
Increase(Balance),
Decrease,
}