1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
// Copyright 2021 ChainSafe Systems
// SPDX-License-Identifier: LGPL-3.0-only
//! # Xcm support for `pallet_staking` calls.
//!
//! This module provides support for calling into the FRAME `pallet_staking` pallet of a remote
//! chain via XCM.
//!
//! Staking involves the bonding of funds for a certain amount of blocks.
//!
//! The `pallet_staking` pallet is configured with the [`Config::BondingDuration`], the in number of
//! eras that must pass until the funds can actually be removed (`withdraw_unbonded`), after they
//! were `unbonded`.
//! - An **Era** is defined as (whole) number of sessions, which is the period that the validator
//! set (and each validator's active nominator set) is recalculated and where rewards are paid
//! out. An era is ~7 days (relay chain) and the `BondingDuration` on polkadot is 28 Eras and 7
//! Eras on Kusama.
//! - A **Session** is a period of time that has a constant set of validators and is measured in
//! block numbers. Sessions are handled by the FRAME `pallet_session` pallet which implement the
//! `ShouldEndSession` trait which determines when a session has ended, and new started. This is
//! used to determine the overall session length.
//!
//! Kusama and polkadot rely on **BABE** (`pallet_babe`) to determine when a session has ended. the
//! BABE pallet implements the `ShouldEndSession` trait and determines whether a session should end
//! by determine whether the epoch should change. An epoch should change if more than
//! `EpochDuration` time has passed. `EpochDuration` measures the amount of time in slots, that each
//! epoch should last. **An epoch length cannot be changed after the chain has started.** Meaning
//! this is chain specific constant. An Epoch on kusama is 1 hour, and 4 hours
//!
//! Knowledge of the `EpochDuration` and the `BondingDuration` and the `MILLISECS_PER_BLOCK` or
//! rather the `SLOT_DURATION` is required to determine when we call `withdraw_unbonded` after we
//! initiate the `unbond`.
use codec::{Compact, Decode, Encode, Output};
use frame_support::{sp_std::vec::Vec, weights::Weight, RuntimeDebug};
#[cfg(feature = "std")]
use serde::{Deserialize, Serialize};
use crate::{CallEncoder, EncodeWith, PalletCall, PalletCallEncoder};
use frame_support::sp_runtime::traits::AtLeast32BitUnsigned;
/// The index of `pallet_staking` in the polkadot runtime
pub const POLKADOT_PALLET_STAKING_INDEX: u8 = 7u8;
/// Provides encoder types to encode the associated types of the
/// `pallet_staking::Config` trait depending on the configured Context.
pub trait StakingCallEncoder<Source, Balance, AccountId>: PalletCallEncoder {
/// Encodes the `<pallet_staking::Config>::Balance` depending on the context
type CompactBalanceEncoder: EncodeWith<Balance, Self::Context>;
/// Encodes the `<pallet_staking::Config>::Source` depending on the context
type SourceEncoder: EncodeWith<Source, Self::Context>;
/// Encodes the `<pallet_staking::Config>::AccountId` depending on the
/// context
type AccountIdEncoder: EncodeWith<AccountId, Self::Context>;
}
impl<'a, 'b, Source, Balance, AccountId, Config> Encode
for CallEncoder<'a, 'b, StakingCall<Source, Balance, AccountId>, Config>
where
Config: StakingCallEncoder<Source, Balance, AccountId>,
{
fn encode_to<T: Output + ?Sized>(&self, dest: &mut T) {
// include the pallet identifier
dest.push_byte(self.call.pallet_call_index());
match self.call {
StakingCall::Bond(bond) => {
Config::SourceEncoder::encode_to_with(&bond.controller, self.ctx, dest);
Config::CompactBalanceEncoder::encode_to_with(&bond.value, self.ctx, dest);
match &bond.payee {
RewardDestination::Staked => {
dest.push_byte(0);
}
RewardDestination::Stash => {
dest.push_byte(1);
}
RewardDestination::Controller => {
dest.push_byte(2);
}
RewardDestination::Account(ref account) => {
dest.push_byte(3);
Config::AccountIdEncoder::encode_to_with(account, self.ctx, dest);
}
_ => {}
}
}
StakingCall::BondExtra(val) => {
Config::CompactBalanceEncoder::encode_to_with(val, self.ctx, dest);
}
StakingCall::Unbond(val) => {
Config::CompactBalanceEncoder::encode_to_with(val, self.ctx, dest);
}
StakingCall::WithdrawUnbonded(val) => val.encode_to(dest),
StakingCall::Nominate(sources) => {
Compact(sources.len() as u32).encode_to(dest);
for source in sources {
Config::SourceEncoder::encode_to_with(source, self.ctx, dest);
}
}
}
}
}
/// Represents dispatchable calls of the FRAME `pallet_staking` pallet.
///
/// *NOTE*: `Balance` is expected to encode with `HasCompact`
pub enum StakingCall<Source, Balance, AccountId> {
/// The [`bond`](https://crates.parity.io/pallet_staking/pallet/enum.Call.html#variant.bond) extrinsic.
///
/// The dispatch origin for this call must be _Signed_ by the stash account.
// #[codec(index = 0)]
Bond(Bond<Source, Balance, AccountId>),
/// The [`bond_extra`](https://crates.parity.io/pallet_staking/pallet/enum.Call.html#variant.bond_extra) extrinsic.
///
/// The dispatch origin for this call must be _Signed_ by the stash, not the
/// controller.
// #[codec(index = 1)]
BondExtra(Balance),
/// The [`unbond`](https://crates.parity.io/pallet_staking/pallet/enum.Call.html#variant.unbond) extrinsic.
///
/// The dispatch origin for this call must be _Signed_ by the controller,
/// not the stash.
// #[codec(index = 2)]
Unbond(Balance),
/// The [`withdraw_unbonded`](https://crates.parity.io/pallet_staking/pallet/enum.Call.html#variant.withdraw_unbonded) extrinsic.
///
/// The dispatch origin for this call must be _Signed_ by the controller,
/// not the stash.
/// `num_slashing_spans` the number of slashing spans to remove.
// #[codec(index = 3)]
WithdrawUnbonded(u32),
/// The [`nominate`](https://crates.parity.io/pallet_staking/pallet/enum.Call.html#variant.nominate) extrinsic.
///
/// The dispatch origin for this call must be _Signed_ by the controller,
/// not the stash.
// #[codec(index = 5)]
Nominate(Vec<Source>),
}
impl<Source, Balance, AccountId> PalletCall for StakingCall<Source, Balance, AccountId> {
/// the indices of the corresponding calls within the `pallet_staking`
fn pallet_call_index(&self) -> u8 {
match self {
StakingCall::Bond(_) => 0,
StakingCall::BondExtra(_) => 1,
StakingCall::Unbond(_) => 2,
StakingCall::WithdrawUnbonded(_) => 3,
StakingCall::Nominate(_) => 5,
}
}
}
/// The [`bond_extra`](https://crates.parity.io/pallet_staking/pallet/enum.Call.html#variant.bond_extra) extrinsic.
///
/// The dispatch origin for this call must be _Signed_ by the stash account.
#[derive(PartialEq, Eq, Clone, RuntimeDebug, scale_info::TypeInfo)]
pub struct Bond<Source, Balance, AccountId> {
/// The lookup type of the controller,
pub controller: Source,
/// The amount to bond.
pub value: Balance,
/// How to payout staking rewards
pub payee: RewardDestination<AccountId>,
}
/// A destination account for payment. mirrored from `pallet_staking`
#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub enum RewardDestination<AccountId> {
/// Pay into the stash account, increasing the amount at stake accordingly.
Staked,
/// Pay into the stash account, not increasing the amount at stake.
Stash,
/// Pay into the controller account.
Controller,
/// Pay into a specified account.
Account(AccountId),
/// Receive no reward.
None,
}
/// The `pallet_staking` configuration for a particular chain
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub struct StakingConfig<AccountId, Balance, BlockNumber> {
/// The index of `pallet_index` within the parachain's runtime
pub pallet_index: u8,
/// The configured reward destination
pub reward_destination: RewardDestination<AccountId>,
/// The specified `minimum_balance` specified the parachain's `T::Currency`
pub minimum_balance: Balance,
/// The configured weights for `pallet_staking`
pub weights: StakingWeights,
/// This measures the time that must pass until `unbonded` funds can be withdrawn measured in
/// **BlockNumber**s
///
/// *NOTE:* It is expected that this is already the duration measured in PINT parachain blocks
/// and *NOT* in remote chain blocks. This involves converting the block time of the other chain
/// to the block time of PINT. The `bonding_duration` as it's expected here is
/// ```nocompile
/// BondDuration * EPOCH_DURATION_IN_BLOCKS * SessionsPerEra * (MILLISEC_PER_BLOCK_other / MILLISEC_PER_BLOCK_pint)
/// ```
pub bonding_duration: BlockNumber,
/// Whether the asset is frozen for xcm related transfers.
pub is_frozen: bool,
}
// Counter for the number of eras that have passed
pub type EraIndex = u32;
/// Just a Balance/BlockNumber tuple to encode when a chunk of funds will be unlocked.
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo)]
pub struct UnlockChunk<Balance, BlockNumber> {
/// Amount of funds to be unlocked.
pub value: Balance,
/// The block number at which point it'll be unlocked.
///
/// *NOTE:* this is expected to be PINT block number
pub end: BlockNumber,
}
/// Represents the state of staking of the PINT's sovereign account on another chain
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo)]
pub struct StakingLedger<Source, Balance, BlockNumber> {
/// The controller account
pub controller: Source,
/// The total amount of the stash's balance that will be at stake
pub active: Balance,
/// The total amount of the stash's balance that we are currently accounting for.
/// It's just `active` plus all the `unlocking` balances.
pub total: Balance,
/// Any balance that is becoming free, which may eventually be transferred out
/// of the stash (assuming it doesn't get slashed first).
///
/// *NOTE:* No more than a limited number of unlocking chunks can co-exists at the same time.
/// See `pallet_staking::MAX_UNLOCKING_CHUNKS`.
pub unlocking: Vec<UnlockChunk<Balance, BlockNumber>>,
}
impl<Source, Balance, BlockNumber> StakingLedger<Source, Balance, BlockNumber>
where
Balance: AtLeast32BitUnsigned + Copy,
BlockNumber: AtLeast32BitUnsigned,
{
/// Mirror an `bond` or `bond_extra` that increased the bonded amount
pub fn bond_extra(&mut self, amount: Balance) {
self.active = self.active.saturating_add(amount);
}
/// The amount currently unbonding
pub fn unlocking(&self) -> Balance {
self.total.saturating_sub(self.active)
}
/// Remove entries from `unlocking` that are sufficiently old and reduce the
/// total by the sum of their balances.
/// Returns `true` if at least one chunk was consolidated
pub fn consolidate_unlocked(&mut self, current_block: BlockNumber) -> bool {
let chunks = self.unlocking.len();
let mut total = self.total;
self.unlocking.retain(|chunk| {
if chunk.end > current_block {
true
} else {
total = total.saturating_sub(chunk.value);
false
}
});
self.total = total;
chunks > self.unlocking.len()
}
}
/// Represents an excerpt from the `pallet_staking` weights
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub struct StakingWeights {
/// Weight for `bond` extrinsic
pub bond: Weight,
/// Weight for `bond_extra` extrinsic
pub bond_extra: Weight,
/// Weight for `unbond` extrinsic
pub unbond: Weight,
/// Weight for `withdraw_unbonded` extrinsic
pub withdraw_unbonded: Weight,
}
/// Represents all staking related durations required to determine the correct chain-specific
/// bonding duration.
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub struct StakingDurations {
/// This determines the average expected block time that the chain is targeting.
///
/// This is essentially the block time in milliseconds.
pub slot_duration: u64,
/// The amount of time in slots, that each epoch should last
///
/// This is essentially the duration of an Era
pub epoch_duration: u64,
/// The number of eras the bonding duration is long
pub bonding_duration: u64,
}