Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

[Feature] Add deposit to fast-unstake #12366

Merged
merged 22 commits into from
Sep 27, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 46 additions & 32 deletions frame/fast-unstake/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ pub mod pallet {
use super::*;
use crate::types::*;
use frame_election_provider_support::ElectionProvider;
use frame_support::pallet_prelude::*;
use frame_support::{pallet_prelude::*, traits::ReservableCurrency};
use frame_system::{pallet_prelude::*, RawOrigin};
use pallet_staking::Pallet as Staking;
use sp_runtime::{
Expand Down Expand Up @@ -113,10 +113,12 @@ pub mod pallet {
+ IsType<<Self as frame_system::Config>::RuntimeEvent>
+ TryInto<Event<Self>>;

/// The amount of balance slashed per each era that was wastefully checked.
///
/// A reasonable value could be `runtime_weight_to_fee(weight_per_era_check)`.
type SlashPerEra: Get<BalanceOf<Self>>;
/// The currency used for deposits.
type DepositCurrency: ReservableCurrency<Self::AccountId, Balance = Self::CurrencyBalance>;

/// Deposit to take for unstaking, to make sure we're able to slash the it in order to cover
/// the costs of resources on unsuccessful unstake.
type Deposit: Get<BalanceOf<Self>>;

/// The origin that can control this pallet.
type ControlOrigin: frame_support::traits::EnsureOrigin<Self::RuntimeOrigin>;
Expand All @@ -132,9 +134,9 @@ pub mod pallet {

/// The map of all accounts wishing to be unstaked.
///
/// Keeps track of `AccountId` wishing to unstake.
/// Keeps track of `AccountId` wishing to unstake and it's corresponding deposit.
#[pallet::storage]
pub type Queue<T: Config> = CountedStorageMap<_, Twox64Concat, T::AccountId, ()>;
pub type Queue<T: Config> = CountedStorageMap<_, Twox64Concat, T::AccountId, BalanceOf<T>>;

/// Number of eras to check per block.
///
Expand Down Expand Up @@ -177,6 +179,8 @@ pub mod pallet {
NotQueued,
/// The provided un-staker is already in Head, and cannot deregister.
AlreadyHead,
/// The call is not allowed at this point.
CallNotAllowed,
}

#[pallet::hooks]
Expand Down Expand Up @@ -212,6 +216,8 @@ pub mod pallet {
/// the chain's resources.
#[pallet::weight(<T as Config>::WeightInfo::register_fast_unstake())]
pub fn register_fast_unstake(origin: OriginFor<T>) -> DispatchResult {
ensure!(ErasToCheckPerBlock::<T>::get() != 0, <Error<T>>::CallNotAllowed);

let ctrl = ensure_signed(origin)?;

let ledger =
Expand All @@ -231,8 +237,10 @@ pub mod pallet {
Staking::<T>::chill(RawOrigin::Signed(ctrl.clone()).into())?;
Staking::<T>::unbond(RawOrigin::Signed(ctrl).into(), ledger.total)?;

T::DepositCurrency::reserve(&ledger.stash, T::Deposit::get())?;

// enqueue them.
Queue::<T>::insert(ledger.stash, ());
Queue::<T>::insert(ledger.stash, T::Deposit::get());
Ok(())
}

Expand All @@ -245,6 +253,8 @@ pub mod pallet {
/// `Staking::rebond`.
#[pallet::weight(<T as Config>::WeightInfo::deregister())]
pub fn deregister(origin: OriginFor<T>) -> DispatchResult {
ensure!(ErasToCheckPerBlock::<T>::get() != 0, <Error<T>>::CallNotAllowed);

let ctrl = ensure_signed(origin)?;
let stash = pallet_staking::Ledger::<T>::get(&ctrl)
.map(|l| l.stash)
Expand Down Expand Up @@ -315,18 +325,23 @@ pub mod pallet {
return T::DbWeight::get().reads(2)
}

let UnstakeRequest { stash, mut checked } = match Head::<T>::take().or_else(|| {
// NOTE: there is no order guarantees in `Queue`.
Queue::<T>::drain()
.map(|(stash, _)| UnstakeRequest { stash, checked: Default::default() })
.next()
}) {
None => {
// There's no `Head` and nothing in the `Queue`, nothing to do here.
return T::DbWeight::get().reads(4)
},
Some(head) => head,
};
let UnstakeRequest { stash, mut checked, deposit } =
match Head::<T>::take().or_else(|| {
// NOTE: there is no order guarantees in `Queue`.
Queue::<T>::drain()
.map(|(stash, deposit)| UnstakeRequest {
stash,
deposit: deposit.into(),
checked: Default::default(),
})
.next()
}) {
None => {
// There's no `Head` and nothing in the `Queue`, nothing to do here.
return T::DbWeight::get().reads(4)
},
Some(head) => head,
};

log!(
debug,
Expand Down Expand Up @@ -381,9 +396,12 @@ pub mod pallet {
num_slashing_spans,
);

T::DepositCurrency::unreserve(&stash, deposit.into());

log!(info, "unstaked {:?}, outcome: {:?}", stash, result);

Self::deposit_event(Event::<T>::Unstaked { stash, result });

<T as Config>::WeightInfo::on_idle_unstake()
} else {
// eras remaining to be checked.
Expand All @@ -406,22 +424,18 @@ pub mod pallet {
// the last 28 eras, have registered yourself to be unstaked, midway being checked,
// you are exposed.
if is_exposed {
let amount = T::SlashPerEra::get()
.saturating_mul(eras_checked.saturating_add(checked.len() as u32).into());
pallet_staking::slashing::do_slash::<T>(
&stash,
amount,
&mut Default::default(),
&mut Default::default(),
current_era,
);
log!(info, "slashed {:?} by {:?}", stash, amount);
Self::deposit_event(Event::<T>::Slashed { stash, amount });
T::DepositCurrency::slash_reserved(&stash, deposit.into());
log!(info, "slashed {:?} by {:?}", stash, deposit);
Self::deposit_event(Event::<T>::Slashed { stash, amount: deposit.into() });
} else {
// Not exposed in these eras.
match checked.try_extend(unchecked_eras_to_check.clone().into_iter()) {
Ok(_) => {
Head::<T>::put(UnstakeRequest { stash: stash.clone(), checked });
Head::<T>::put(UnstakeRequest {
stash: stash.clone(),
checked,
deposit,
});
Self::deposit_event(Event::<T>::Checking {
stash,
eras: unchecked_eras_to_check,
Expand Down
5 changes: 3 additions & 2 deletions frame/fast-unstake/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,12 +164,13 @@ impl Convert<sp_core::U256, Balance> for U256ToBalance {
}

parameter_types! {
pub static SlashPerEra: u32 = 100;
pub static DepositAmount: u32 = 28_000;
}

impl fast_unstake::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type SlashPerEra = SlashPerEra;
type Deposit = DepositAmount;
type DepositCurrency = Balances;
type ControlOrigin = frame_system::EnsureRoot<Self::AccountId>;
type WeightInfo = ();
}
Expand Down
2 changes: 2 additions & 0 deletions frame/fast-unstake/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ pub struct UnstakeRequest<AccountId: Eq + PartialEq + Debug, MaxChecked: Get<u32
pub(crate) stash: AccountId,
/// The list of eras for which they have been checked.
pub(crate) checked: BoundedVec<EraIndex, MaxChecked>,
/// Deposit to be slashed if the unstake was unsuccessful.
pub(crate) deposit: u64,
}

#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo, RuntimeDebugNoBound)]
Expand Down
1 change: 1 addition & 0 deletions frame/staking/src/pallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ pub mod pallet {
+ sp_std::fmt::Debug
+ Default
+ From<u64>
+ Into<u64>
+ TypeInfo
+ MaxEncodedLen;
/// Time used for computing era duration.
Expand Down