Skip to content

Commit

Permalink
feat: make candidate conditional logic
Browse files Browse the repository at this point in the history
  • Loading branch information
wangminqi committed Jan 9, 2025
1 parent 4d36bdc commit 9dc8d4c
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 182 deletions.
109 changes: 109 additions & 0 deletions parachain/pallets/parachain-staking/src/delegation_requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,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
103 changes: 13 additions & 90 deletions parachain/pallets/parachain-staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -984,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)?;
if T::LeaveCandidatesDelay::get() == 0u32 {
Self::candidate_execute_schedule_revoke(candidate)
} 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 @@ -1012,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 @@ -1157,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, less)?;
if T::CandidateBondLessDelay::get() {
Self::candidate_execute_bond_decrease(candidate)?
} else {
Ok(().into())
}
}
#[pallet::call_index(17)]
#[pallet::weight(< T as Config >::WeightInfo::execute_candidate_bond_less())]
Expand All @@ -1175,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
40 changes: 13 additions & 27 deletions parachain/pallets/parachain-staking/src/tests_zero_delay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,11 @@
// You should have received a copy of the GNU General Public License
// along with Litentry. If not, see <https://www.gnu.org/licenses/>.
use crate::{
mock_zero_delay::{ExtBuilder, ParachainStaking, RuntimeCall, RuntimeOrigin, Test, Utility},
mock_zero_delay::{ExtBuilder, ParachainStaking, RuntimeCall, RuntimeOrigin, Test},
Error,
};
use frame_support::{assert_noop, assert_ok};

use crate::Call as ParachainStakingCall;

#[test]
fn batch_unstake_and_leave_delegators_works_if_zero_delay() {
ExtBuilder::default()
Expand All @@ -45,18 +43,12 @@ fn batch_unstake_and_leave_candidates_works_if_zero_delay() {
.with_candidates(vec![(1, 10)])
.build()
.execute_with(|| {
// can execute immediately
assert_ok!(Utility::batch_all(
RuntimeOrigin::signed(1),
vec![
RuntimeCall::ParachainStaking(
ParachainStakingCall::schedule_leave_candidates {}
),
RuntimeCall::ParachainStaking(ParachainStakingCall::execute_leave_candidates {
candidate: 1
}),
]
));
// Execute immediately
assert_ok!(ParachainStaking::schedule_leave_candidates(RuntimeOrigin::signed(1)));
assert_noop!(
ParachainStaking::execute_leave_candidates(RuntimeOrigin::signed(1), 1),
Error::<Test>::CandidateDNE
);
});
}

Expand Down Expand Up @@ -105,17 +97,11 @@ fn batch_unstake_and_candidate_bond_less_works_if_zero_delay() {
.with_candidates(vec![(1, 20)])
.build()
.execute_with(|| {
// can execute immediately
assert_ok!(Utility::batch_all(
RuntimeOrigin::signed(1),
vec![
RuntimeCall::ParachainStaking(
ParachainStakingCall::schedule_candidate_bond_less { less: 1 }
),
RuntimeCall::ParachainStaking(
ParachainStakingCall::execute_candidate_bond_less { candidate: 1 }
),
]
));
// Execute immediately
assert_ok!(ParachainStaking::schedule_candidate_bond_less(RuntimeOrigin::signed(1), 1));
assert_noop!(
ParachainStaking::execute_candidate_bond_less(RuntimeOrigin::signed(1), 1),
Error::<Test>::PendingCandidateRequestNotDueYet
);
});
}
66 changes: 1 addition & 65 deletions parachain/ts-tests/integration-tests/precompile-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,74 +293,10 @@ describeLitentry('Test Parachain Precompile Contract', ``, (context) => {
precompileStakingContractAddress,
'scheduleDelegatorBondLess'
);
expect(await isPendingRequest()).to.be.true;

// cancelDelegationRequest(collator)
const cancelDelegationRequest = precompileStakingContract.interface.encodeFunctionData(
'cancelDelegationRequest',
[collatorPublicKey]
);

expect(await isPendingRequest()).to.be.true;
await executeTransaction(cancelDelegationRequest, precompileStakingContractAddress, 'cancelDelegationRequest');
// Zero delay impl will make execution immediately
expect(await isPendingRequest()).to.be.false;

// only makes sense when parachain is compiled with `fast-runtime` feature, otherwise we'll
// never make it within reasonable time
if (config.parachain_fast_runtime === 'true') {
// testing bond less + execution
await executeTransaction(
scheduleDelegatorBondLess,
precompileStakingContractAddress,
'scheduleDelegatorBondLess again to test execution'
);
expect(await isPendingRequest()).to.be.true;

// executeDelegationRequest(delegator, collator);
const executeDelegationRequest = precompileStakingContract.interface.encodeFunctionData(
'executeDelegationRequest',
[evmAccountRaw.publicKey, collatorPublicKey]
);
await executeTransaction(
executeDelegationRequest,
precompileStakingContractAddress,
'executeDelegationRequest'
);
const { data: balanceAfterBondLess } = await context.api.query.system.account(evmAccountRaw.mappedAddress);
expect(balanceAfterBondLess.reserved.toBigInt()).to.eq(
balanceAfterBondMore.reserved.toBigInt() - toBigInt(5)
);

// testing revoke delegation + execute
// scheduleRevokeDelegation(collator);
const scheduleRevokeDelegation = precompileStakingContract.interface.encodeFunctionData(
'scheduleRevokeDelegation',
[collatorPublicKey]
);
await executeTransaction(
scheduleRevokeDelegation,
precompileStakingContractAddress,
'scheduleRevokeDelegation'
);

await executeTransaction(
executeDelegationRequest,
precompileStakingContractAddress,
'executeDelegationRequest'
);
const { data: balanceAfterRevoke } = await context.api.query.system.account(evmAccountRaw.mappedAddress);
expect(balanceAfterRevoke.reserved.toBigInt()).to.eq(toBigInt(0));

// delegate(collator, amount);
const delegate = precompileStakingContract.interface.encodeFunctionData('delegate', [
collatorPublicKey,
ethers.utils.parseUnits('57', 18).toString(),
]);
await executeTransaction(delegate, precompileStakingContractAddress, 'delegate');
const { data: balanceAfterDelegate } = await context.api.query.system.account(evmAccountRaw.mappedAddress);
expect(balanceAfterDelegate.reserved.toBigInt()).to.eq(toBigInt(57));
}

console.timeEnd('Test precompile staking contract');
});

Expand Down

0 comments on commit 9dc8d4c

Please sign in to comment.