Skip to content

Commit

Permalink
Utility batch test and zero delay impl (#3226)
Browse files Browse the repository at this point in the history
* feat: add utility batch test and zero delay impl

* chore: fix

* chore: fix fmt

* chore: fix clippy

* feat: make extra conditional judge

* chore: fix

* chore: fix

* chore: fix

* feat: make candidate conditional logic

* chore: fix

* chore: fix

* chore: remove utility

---------

Co-authored-by: Kai <[email protected]>
  • Loading branch information
wangminqi and Kailai-Wang authored Jan 11, 2025
1 parent 86ec963 commit 638b2f1
Show file tree
Hide file tree
Showing 7 changed files with 463 additions and 169 deletions.
121 changes: 116 additions & 5 deletions parachain/pallets/parachain-staking/src/delegation_requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,23 @@
use crate::{
pallet::{
BalanceOf, CandidateInfo, Config, DelegationScheduledRequests, DelegatorState, Error,
Event, Pallet, Round, RoundIndex, Total,
AutoCompoundingDelegations, BalanceOf, BottomDelegations, CandidateInfo, CandidatePool,
Config, DelegationScheduledRequests, DelegatorState, Error, Event, Pallet, Round,
RoundIndex, TopDelegations, Total,
},
weights::WeightInfo,
AutoCompoundDelegations, Delegator, OnAllDelegationRemoved,
AutoCompoundDelegations, Bond, Delegator, OnAllDelegationRemoved,
};
use frame_support::{
dispatch::DispatchResultWithPostInfo,
ensure,
pallet_prelude::DispatchResult,
traits::{Get, ReservableCurrency},
};
use parity_scale_codec::{Decode, Encode};
use scale_info::TypeInfo;
use sp_core::RuntimeDebug;
use sp_runtime::traits::Saturating;
use sp_runtime::traits::{Saturating, Zero};
use sp_std::{vec, vec::Vec};

/// An action that can be performed upon a delegation
Expand Down Expand Up @@ -84,6 +86,115 @@ impl<A, B> From<ScheduledRequest<A, B>> for CancelledScheduledRequest<B> {
}

impl<T: Config> Pallet<T> {
/// Schedules a [CollatorStatus::Leaving] for the candidate
pub(crate) fn candidate_schedule_revoke(collator: T::AccountId) -> DispatchResultWithPostInfo {
let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
let (now, when) = state.schedule_leave::<T>()?;
let mut candidates = <CandidatePool<T>>::get();
if candidates.remove(&Bond::from_owner(collator.clone())) {
<CandidatePool<T>>::put(candidates);
}
<CandidateInfo<T>>::insert(&collator, state);
Self::deposit_event(Event::CandidateScheduledExit {
exit_allowed_round: now,
candidate: collator,
scheduled_exit: when,
});
Ok(().into())
}

/// Executes a [CollatorStatus::Leaving] for the candidate
pub(crate) fn candidate_execute_schedule_revoke(
candidate: T::AccountId,
) -> DispatchResultWithPostInfo {
let state = <CandidateInfo<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
state.can_leave::<T>()?;
let return_stake = |bond: Bond<T::AccountId, BalanceOf<T>>| -> DispatchResult {
T::Currency::unreserve(&bond.owner, bond.amount);
// remove delegation from delegator state
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) = delegator.rm_delegation(&candidate) {
Self::delegation_remove_request_with_state(&candidate, &bond.owner, &mut delegator);
<AutoCompoundDelegations<T>>::remove_auto_compound(&candidate, &bond.owner);

if remaining.is_zero() {
// we do not remove the scheduled delegation requests from other collators
// since it is assumed that they were removed incrementally before only the
// last delegation was left.
<DelegatorState<T>>::remove(&bond.owner);
let _ = T::OnAllDelegationRemoved::on_all_delegation_removed(&bond.owner);
} else {
<DelegatorState<T>>::insert(&bond.owner, delegator);
}
}
Ok(())
};
// total backing stake is at least the candidate self bond
let mut total_backing = state.bond;
// return all top delegations
let top_delegations =
<TopDelegations<T>>::take(&candidate).expect("CandidateInfo existence checked");
for bond in top_delegations.delegations {
return_stake(bond)?;
}
total_backing = total_backing.saturating_add(top_delegations.total);
// return all bottom delegations
let bottom_delegations =
<BottomDelegations<T>>::take(&candidate).expect("CandidateInfo existence checked");
for bond in bottom_delegations.delegations {
return_stake(bond)?;
}
total_backing = total_backing.saturating_add(bottom_delegations.total);
// return stake to collator
T::Currency::unreserve(&candidate, state.bond);
<CandidateInfo<T>>::remove(&candidate);
<DelegationScheduledRequests<T>>::remove(&candidate);
<AutoCompoundingDelegations<T>>::remove(&candidate);
<TopDelegations<T>>::remove(&candidate);
<BottomDelegations<T>>::remove(&candidate);
let new_total_staked = <Total<T>>::get().saturating_sub(total_backing);
<Total<T>>::put(new_total_staked);
Self::deposit_event(Event::CandidateLeft {
ex_candidate: candidate,
unlocked_amount: total_backing,
new_total_amt_locked: new_total_staked,
});
let actual_weight =
Some(T::WeightInfo::execute_leave_candidates(state.delegation_count as u32));
Ok(actual_weight.into())
}

/// Schedules a [CandidateBondLessRequest] for the candidate
pub(crate) fn candidate_schedule_bond_decrease(
collator: T::AccountId,
less: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
let when = state.schedule_bond_less::<T>(less)?;
<CandidateInfo<T>>::insert(&collator, state);
Self::deposit_event(Event::CandidateBondLessRequested {
candidate: collator,
amount_to_decrease: less,
execute_round: when,
});
Ok(().into())
}

/// Executes a [CandidateBondLessRequest] for the candidate
pub(crate) fn candidate_execute_bond_decrease(
candidate: T::AccountId,
) -> DispatchResultWithPostInfo {
let mut state = <CandidateInfo<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
state.execute_bond_less::<T>(candidate.clone())?;
<CandidateInfo<T>>::insert(&candidate, state);
Ok(().into())
}

/// Schedules a [DelegationAction::Revoke] for the delegator, towards a given collator.
pub(crate) fn delegation_schedule_revoke(
collator: T::AccountId,
Expand Down Expand Up @@ -144,7 +255,7 @@ impl<T: Config> Pallet<T> {
ensure!(decrease_amount <= max_subtracted_amount, <Error<T>>::DelegatorBondBelowMin);

let now = <Round<T>>::get().current;
let when = now.saturating_add(T::RevokeDelegationDelay::get());
let when = now.saturating_add(T::DelegationBondLessDelay::get());
scheduled_requests.push(ScheduledRequest {
delegator: delegator.clone(),
action: DelegationAction::Decrease(decrease_amount),
Expand Down
134 changes: 41 additions & 93 deletions parachain/pallets/parachain-staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ pub mod set;
#[cfg(test)]
mod tests;

// Dedicated test for unbond delay = 0
#[cfg(test)]
mod mock_zero_delay;
#[cfg(test)]
mod tests_zero_delay;

pub use inflation::{InflationInfo, Range};
pub use weights::WeightInfo;

Expand Down Expand Up @@ -978,21 +984,13 @@ pub mod pallet {
/// removed from the candidate pool to prevent selection as a collator.
pub fn schedule_leave_candidates(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
let collator = ensure_signed(origin)?;
let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
let (now, when) = state.schedule_leave::<T>()?;
let mut candidates = <CandidatePool<T>>::get();
if candidates.remove(&Bond::from_owner(collator.clone())) {
<CandidatePool<T>>::put(candidates);
let _ = Self::candidate_schedule_revoke(collator.clone())?;
if T::LeaveCandidatesDelay::get() == 0u32 {
Self::candidate_execute_schedule_revoke(collator)
} else {
Ok(().into())
}
<CandidateInfo<T>>::insert(&collator, state);
Self::deposit_event(Event::CandidateScheduledExit {
exit_allowed_round: now,
candidate: collator,
scheduled_exit: when,
});
Ok(().into())
}

#[pallet::call_index(11)]
#[pallet::weight(
< T as Config >::WeightInfo::execute_leave_candidates(
Expand All @@ -1006,70 +1004,7 @@ pub mod pallet {
candidate: T::AccountId,
) -> DispatchResultWithPostInfo {
ensure_signed(origin)?;
let state = <CandidateInfo<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
state.can_leave::<T>()?;
let return_stake = |bond: Bond<T::AccountId, BalanceOf<T>>| -> DispatchResult {
T::Currency::unreserve(&bond.owner, bond.amount);
// remove delegation from delegator state
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) = delegator.rm_delegation(&candidate) {
Self::delegation_remove_request_with_state(
&candidate,
&bond.owner,
&mut delegator,
);
<AutoCompoundDelegations<T>>::remove_auto_compound(&candidate, &bond.owner);

if remaining.is_zero() {
// we do not remove the scheduled delegation requests from other collators
// since it is assumed that they were removed incrementally before only the
// last delegation was left.
<DelegatorState<T>>::remove(&bond.owner);
let _ = T::OnAllDelegationRemoved::on_all_delegation_removed(&bond.owner);
} else {
<DelegatorState<T>>::insert(&bond.owner, delegator);
}
}
Ok(())
};
// total backing stake is at least the candidate self bond
let mut total_backing = state.bond;
// return all top delegations
let top_delegations =
<TopDelegations<T>>::take(&candidate).expect("CandidateInfo existence checked");
for bond in top_delegations.delegations {
return_stake(bond)?;
}
total_backing = total_backing.saturating_add(top_delegations.total);
// return all bottom delegations
let bottom_delegations =
<BottomDelegations<T>>::take(&candidate).expect("CandidateInfo existence checked");
for bond in bottom_delegations.delegations {
return_stake(bond)?;
}
total_backing = total_backing.saturating_add(bottom_delegations.total);
// return stake to collator
T::Currency::unreserve(&candidate, state.bond);
<CandidateInfo<T>>::remove(&candidate);
<DelegationScheduledRequests<T>>::remove(&candidate);
<AutoCompoundingDelegations<T>>::remove(&candidate);
<TopDelegations<T>>::remove(&candidate);
<BottomDelegations<T>>::remove(&candidate);
let new_total_staked = <Total<T>>::get().saturating_sub(total_backing);
<Total<T>>::put(new_total_staked);
Self::deposit_event(Event::CandidateLeft {
ex_candidate: candidate,
unlocked_amount: total_backing,
new_total_amt_locked: new_total_staked,
});
let actual_weight =
Some(T::WeightInfo::execute_leave_candidates(state.delegation_count as u32));
Ok(actual_weight.into())
Self::candidate_execute_schedule_revoke(candidate)
}
#[pallet::call_index(12)]
#[pallet::weight(< T as Config >::WeightInfo::cancel_leave_candidates(< CandidatePool < T >>::get().0.len() as u32))]
Expand Down Expand Up @@ -1151,15 +1086,12 @@ pub mod pallet {
less: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let collator = ensure_signed(origin)?;
let mut state = <CandidateInfo<T>>::get(&collator).ok_or(Error::<T>::CandidateDNE)?;
let when = state.schedule_bond_less::<T>(less)?;
<CandidateInfo<T>>::insert(&collator, state);
Self::deposit_event(Event::CandidateBondLessRequested {
candidate: collator,
amount_to_decrease: less,
execute_round: when,
});
Ok(().into())
let _ = Self::candidate_schedule_bond_decrease(collator.clone(), less)?;
if T::CandidateBondLessDelay::get() == 0u32 {
Self::candidate_execute_bond_decrease(collator)
} else {
Ok(().into())
}
}
#[pallet::call_index(17)]
#[pallet::weight(< T as Config >::WeightInfo::execute_candidate_bond_less())]
Expand All @@ -1169,10 +1101,7 @@ pub mod pallet {
candidate: T::AccountId,
) -> DispatchResultWithPostInfo {
ensure_signed(origin)?; // we may want to reward this if caller != candidate
let mut state = <CandidateInfo<T>>::get(&candidate).ok_or(Error::<T>::CandidateDNE)?;
state.execute_bond_less::<T>(candidate.clone())?;
<CandidateInfo<T>>::insert(&candidate, state);
Ok(().into())
Self::candidate_execute_bond_decrease(candidate)
}
#[pallet::call_index(18)]
#[pallet::weight(< T as Config >::WeightInfo::cancel_candidate_bond_less())]
Expand Down Expand Up @@ -1242,7 +1171,12 @@ pub mod pallet {
/// Success forbids future delegation requests until the request is invoked or cancelled.
pub fn schedule_leave_delegators(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
let delegator = ensure_signed(origin)?;
Self::delegator_schedule_revoke_all(delegator)
let _ = Self::delegator_schedule_revoke_all(delegator.clone())?;
if T::LeaveDelegatorsDelay::get() == 0u32 {
Self::delegator_execute_scheduled_revoke_all(delegator)
} else {
Ok(().into())
}
}
#[pallet::call_index(22)]
#[pallet::weight(< T as Config >::WeightInfo::execute_leave_delegators(
Expand Down Expand Up @@ -1275,7 +1209,12 @@ pub mod pallet {
collator: T::AccountId,
) -> DispatchResultWithPostInfo {
let delegator = ensure_signed(origin)?;
Self::delegation_schedule_revoke(collator, delegator)
let _ = Self::delegation_schedule_revoke(collator.clone(), delegator.clone())?;
if T::RevokeDelegationDelay::get() == 0u32 {
Self::delegation_execute_scheduled_request(collator, delegator)
} else {
Ok(().into())
}
}

#[pallet::call_index(25)]
Expand Down Expand Up @@ -1311,7 +1250,16 @@ pub mod pallet {
less: BalanceOf<T>,
) -> DispatchResultWithPostInfo {
let delegator = ensure_signed(origin)?;
Self::delegation_schedule_bond_decrease(candidate, delegator, less)
let _ = Self::delegation_schedule_bond_decrease(
candidate.clone(),
delegator.clone(),
less,
)?;
if T::DelegationBondLessDelay::get() == 0u32 {
Self::delegation_execute_scheduled_request(candidate, delegator)
} else {
Ok(().into())
}
}

#[pallet::call_index(27)]
Expand Down
Loading

0 comments on commit 638b2f1

Please sign in to comment.