# Staking Parachain high-level design and engineering
- [[Meta] Moving Staking off the Relay Chain #491 (issue)](https://github.com/paritytech/polkadot-sdk/issues/491)
- [Transactionless relay chain (issue)](https://github.com/paritytech/polkadot/issues/323)
- https://hackmd.io/-U1tHMaiQByuW5zGXocOqQ
## Goals
- Set the standards and best practices for parachains on cross-chain communication and asset management to the community;
- Backwards compatible with the current relay-chain based staking system; Close to seamless migration to parachain;
- Minimise the cross-chain messages with the assets hub; The storage and computation bottlenecks are dictated by the staking chain blockspace itself, not the communication with external parachains or relay chain.
- Minimal viable staking interface is provided through the AssetsHub (`Stake`, `Unstake`). The augmented and staking protocol specific functionality is provided through the staking chain (e.g. `Stake`, `Unstake`, `ManagePools`, `ValidatorPrefs`, etc..). In addition, it is possible to stake and unstake directly through the staking chain.
- The minimal viable staking interface allows on-chain/off-chain users to implement a basic integration by integrating with asset hub alone.
**Non-goals**
- The staking balance (locked and rewards) are _always_ in sync with the locked DOT in AssetHub
- Staking rewards are always in sync with the locked stake in assets hub
- Staking rewards are used in governance right after being awarded
- It is possible to know at all times the *full* staking balance (locked and rewards) of an account through the `AH` alone.
## Functional goals
- DOTs minted at only one system parachain; Staking chain should not mint DOTs.
- Abstract as much as possible the concept of "era" from the relay-chain. The relay chain only needs to know when a new set of validators is ready to be used. New validator set account IDs are sent through XCM message from the `SC` to the `RC::pallet_session`.
- The concept of exposures is local to the staking parachain. There is no need to share exposure data cross-chain. Exposures are stored and used by the staking chain to keep track of who should be rewarded.
- Staking has a fully-fledged `pallet_balances` to keep track of staked and rewarded funds per staker. The funds are derivatives of locked DOT (`sDOT`) in the `AH` or era minted inflation (in the case of unclaimed rewards).
- Nomination pools are first class stakers, similar to direct nominators. For the external consensus parties and wallets, a nomination pool is a direct nominator with different configurations (pooled nominations, operator, less minimum bond to nominate, etc).
- Allow implementing nomination pools as smart contracts (stretch goal).
- Features restaking feature natively.
- Multiple privileged system chains may receive DOT minted through inflation directly. The staking parachain maintains the list of origins that may receive DOT from inflation and periodically (at the end of each era) requests `AH` to mint new DOT into the corresponding pots.
## Inflation
- The era inflation should be minted periodically at the end of every era. Only staking chain keeps track and decides when there is an era transition, thus the staking chain should be responsible for requesting the minting of inflation DOT.
- The minted era inflation can be distributed into different pots. Each system parachain (staking included) that _may_ receive a portion of the era inflation directly has a dedicated pot in `AH` where DOT originated from era inflation will be minted into.
- The `SC` will use the corresponding pot to settle and sync rewards in `AH`.
### Flow:
- Periodically (i.e at the end of an era), `SC` calculates era payouts and sends a XCM message to `AH` to mint and transfer DOT to the correct sovereign accounts.
1. `SC` calculates how much balance should be minted based on current issuance, duration of the era, etc
2. `SC` signals`SC` to mint new DOT into the correct pots. This will be done similarly to teleporting, but no assets are burned in the `SC` since funds are minted to increase inflation and there's no fund parity across chains.
```rust
/// context: staking chain
#[pallet::config]
pub trait Config: frame_system::Config {
/// map of how much percentage each priviledged origin (staking included)
/// should receive from the total payout for each era.
#[pallet::storage]
pub type InflationDestinatons<T> = StorageMap<_, _, AccountId, Percent>;
}
impl<T: Config> Pallet<T> {
/// calculates the total era inflation payout and the remainder to be minted
/// into the treasury.
fn total_era_payout() -> (Balance, Balance) {}
/// issues requests for asset hub to mint new DOT into
/// `InflationDestinations` pots through XCM.
fn request_inflation_payout(total_era_payout: Balance, remainder: Balance) {
// for each InflationDestination
// 1. calculate Percent * total_era_payout
// 2. deposit asset into corresponding pot in `AH`
}
}
```
## Interface with relay chain
### Pallet session
Currently, the `pallet_session` tries to fetch a new validator from staking (impl `SessionManager`) periodically. If the request was made in a new era and the election was successful, staking provides a vec with the new validator set account IDs, otherwise `None`.
In a parachain context, the plan is to make the flow reaction (from the `RC` perspective), rather than proactive: whenever the `SC` decides a new era should start, it sends a new XCM message (or batch) with the new validator set for the era.
If the election fails OR the XCM message is not processed, the current validator set does not change. In case of newly applied slashes, the `SC` will may run a new election without the slashed validators and call `SessionProvider::new_session`.
```rust
/// implemented by pallet session in the relay chain to receive a new validator
/// set that should be activated asap
///
/// interface implemented through XCM
trait SessionHandler {
/// information for new era
fn new_session(era: EraIndex, Vec<AccountId>);
}
```
```rust
/// implemented by SC to send information of new validator set.
///
/// the result should be a XCM message/batch with the account IDs of the new
/// validator set
trait SessionProvider {
/// output of election. information for a new era
fn new_session(era: EraIndex, Vec<AccountId>);
}
```
### Pallet `session::historical`
We assume that the `SC` is trusted, thus there is no need to expose and store the staking exposures in the relay chain. This means that the `RC` trusts that `SC` applies slashes correctly.
### Slashing
All the logic related to slashing is encapsulated in the `SC`. The only interface between `SC` and the `RC` is the `SessionProvider::new_session`, which may be called when validators are slashed.
## Interface with AssetsHub
### Assets
The high-level interface between assets hub and staking chain exposes a way for wallets to stake and unstake. The XCM instructions can be sent directly to `AH` or through `SC`.
```rust
/// implemented by the assets hub; calls through XCM by either staking or `staker` origin.
trait StakeAssets {
/// lock funds and set intentions to start staking.
///
/// at the AH level, the `freeze` balance is locked. The `nominations` are
/// passed to SC through the `NoteUnlockable` metadata (if possible).
fn stake(
staker: AccountId,
freeze: Balance,
nominations: Option<BoundedVec<(AccountId, Balance), MaxNominations>>
);
/// request unstake of staked balance.
///
/// the request is propagated to `SC`, which calls into `SC::unbond`.
/// the funds are not unfrozen right away, due to the potential
/// unbonding period.
fn unstake(staker: AccountId, Balance);
/// requests unfreeze of any unlocked funds.
///
/// the request is propagated to `SC`, which calls into `SC::withdraw`
fn withdraw(staker: AccountId);
}
```
**XCM flow `wallet > AH > SC`**
- **Stake**:
1. XCM message `Stake{}` to `AH`
2. `AH` freezes balance locally
3. `AH` sends `NoteUnlockable {metadata: nominations}` to `SC`
4. `SC` updates nominations based on metadata and multilocation of the sender (staker)
- **Unstake**
1. XCM message `Unstake{balance}` to `AH`
2. Send `Transact{unbound(balance)}` to `SC`
3. `SC` applies unbond locally
- **Withdraw**
1. XCM message `RequestUnlock{}` to `SC`
2. Send `Transact{withdraw}` to `SC`
3. `SC` applies withdraw, checks how much origin can withdraw
4. `SC` sends `UnlockAsset{origin}` to `AH`; queues withdraw request locally
5. `AH` sends `NoteUnlockable` to `SC`;
6. `SC` applies queued withdraw locally
**XCM flow `wallet > SC > AH`**
- **Stake**
1. XCM message `Stake{}` to `SC`
2. `SC` sends `LockAsset{}` to `AH`
3. `AH` sends `NoteUnlockable{}` to `SC`
4. `SC` updates nominations based on cached nominations and multilocation of the sender
- **Unstake**
1. XCM message `Unstake{balance}` *or* `Transact{unbond(balance)}` to `SC`
2. `SC` applies unbond locally
- **Withdraw**
1. XCM message `Transact{withdraw}` to `SC`
2. `SC` sends `UnlockAsset{balance}` to `AH`; queues withdraw request locally
3. `AH` sends `NoteUnlockable` to `SC`;
4. `SC` applies queued withdraw locally
- **Staking application specific**
- `bond()`, `bond_extra()` (same as `Stake{}`)
- `unbond()` (same as `Unstake{}`)
- `withdraw()` (same as `Withdraw{}`)
- `nominate()`
- `validate()`
- `chill()`
- `set_payees()`
- etc...
Requests:
- Add metadata to `NoteUnlockable`
### Slashing
The `RC` runtime includes both the `pallet_granpa` and `pallet_offences`. Currently, the offences pallet sets `pallet_staking` (impl `OnOffenceHandler`) as the `T::OnOffenceHandler` config type.
In the context of the staking parachain, the
```rust=
T::OnOffenceHandler::on_offence(
&concurrent_offenders,
&slash_perbill,
offence.session_index(),
offence.disable_strategy(),
);
```
will instead be called through a (or batch of) `Transact{on_offence(...)}` XCM message(s).
Upon handling the `on_offence` report through the XCM messages sent by the palet offences, `SC` will queue the deferred slashes and proceed as currently.
> TODO: how to ensure that the `on_offence` XCM messages are not dropped?
> TODO: limits with regard to XCM message/batch size?
## Staking Chain implementation details
### Rewards
#### Sync rewards in `SC` and `AH`
The auto-compound rewards earned in `SC` are not automatically synced with the `AH`. Thus, aut-compound rewards cannot be used directly for e.g. governance since the `AH` doesn't keep tallying all the rewards earned over time by an account as locked stake.
In order to sync the rewards between `SC` and `AH`, the wallet must request a reward sync by calling `Call::sync_rewards()` in `SC` (through XCM). The `SC` sends a `TransferAsset{asset, staker}` XCM message to `AH`, requesting that `asset.amount` is transfered from the sovereign account from the staking chain on AssetsHub into the staker frozen amount.
**Flow**:
1. Wallet request `SC` to sync rewards via `Transact{sync_rewards()}`
2. `SC` calculates how many unsync rewards the staker has
3. `SC` sends `TransferAsset{asset, staker} + LockAsset{}` to `AH`
4. `AH` transfers DOT from the staking sovereign account (topped with inflation DOT)
5. `AH` locks funds in staker's account (in lockstep with 4.)
6. `NoteLocked` is sent to `SC`
7. `SC` updates unsync reward counters locally
### Staking bond, withdraw, nominations
### Nomination pools
### Staking prefs and operational configs
### Fees
1. Free quota per account/day
2. [`BuyExecution`](https://github.com/paritytech/polkadot/blob/962bc21352f5f80a580db5a28d05154ede4a9f86/xcm/src/v3/mod.rs#L672)
## New XCM instructions
## Migration strategy
---
## Diagrams
### 1. System
System actions are initiated by the origin consensus system. Note: the numbering does not define the actions ordering
① **Mint reward budget for staking rewards**
At each era, the era payout for stakers is teleported from assets hub to the staking parchain. This is a proactive teleport (i.e. initiated at AssetsHub), thus the assets hub must know the concept of `era` or be signaled by some other chain when the era changes.
② **Set new validator set**
Whenever a new set of validators is ready, the staking chain proactively signals the relay chain of the new set of validator keys. The relay chain offloads the logic of when/why to change the validator set to the staking chain.
③ **Run on/offchain election**
Similar to nowadays, EPM manages the era transitions and starts a new election when appropriate. Multi-block election, possibly on-chain if possible.
④ **Set era points**
Relay chain proactively announces the new era points of block authors.
⑤ **New offence**
The relay chain announces new offences that are handled by the pallet slashes.
⑥ **Disable active validator**
The staking chain announces which active validators should be disabled due to offences.
### 2. **Validator initiated**
Actions initiated by the validators or triggered as a second-order consequence of validator requests.
The staking funds are teleported from assets hub to the staking parachain.
① **Set session keys**
When setting intention to become a validator, the session keys must be set in the staking chain.
② **Propagate session keys**
Propagate the validator session keys to the relay chain. Perhaps as a batch of the most recent registered validators.
③ **Request teleport funds to SC**
The validator request teleport of DOT the asset hub into the staking chain.
④ **Teleport assets to SC**
Assets are teleported to staking chain into the `validator` account.
⑤ **Staking-related calls**
Upon hold, the relevant system parachains (e.g. governance) are notified of the funds hold for staking.
⑥ **Teleport assets to AH**
Upon request to withdraw, teleport assets back to AH.
### 3. **Nominator initiated**
Actions initiated by the nominators or triggered as a second-order consequence of nominator requests.
The staking funds are reserved in the assets hub and a derivative is minted in the staking chain; In order to sync up earned rewards for e.g. governance, the staking parachain needs to interact with the assets hub. All interactions with staking hub need to go through the asset hub.
⭐ Zoom-in on inflation
- consider both remote and native staking options (who pays whom and how)
⭐ Zoom-in on exposing native staking funds in governance
⭐ Start work on the `Stake`, `Unstake`, `Claim` XCM instructions asap and
- integrate with relay chain
- integrate with SC
- integrate with AH
⭐ Fees
---
## Old-ish
- 2 designs:
1. **AssetHub native**: assets live on the Asset Hub
2. **Staking Parachain native**: assets live on the staking parachain: DOT is locked in AssetsHub and a derivative token is minted in the staking parachain.
## Option 1 - AssetHub native
> TODO: interaction diagram (minimum viable interaction between chains)
### Requirements
- Staking parachain runs a fully fledge `pallet_balances` locally;
- Once the
- Fees:
- Free usage quota;
- If user runs out of usage quota, teleport DOT and use it to pay fees.
### Advantages
> TODO
### Disadvantages
> TODO
## Option 2 - Staking Parachain native
> TODO: interaction diagram (minimum viable interaction between chains)
### Advantages
- Locked funds for staking have support for governance etc out of the box;
- Large custodials that participate in staking (e.g. Kraken) only need to integrate with AssetsHub to integrate with all the staking chains in the ecosystem.
- May need some dedicated support to functionality of different staking parachains, but less work and infra to support basic staking across all ecosystem (stake, unstake, check balance).
### Disadvantages
> TODO
### Requirements
- Have one chain (asset hub) that every dapp can integrate with for any asset that is on Asset Hub.
- `XCM::stake` and `XCM::unstake` instructions that allow minimal staking. This should also allow to nominate a set of validators or pools.
- Resource allocation: Based on staked amount, users get free quota of weight per era. If they need more execution time, they need to teleport DOTs for gas.
- Gas payment can also be done by teleporting DOTs to staking chain (so staking hub could still allow assets natively).
- Or have a tokenless fee payment system (see below)
### Era reward minting
- The staking parachain keeps a sovereign account, where inflation is minted to.
- **Mint inflation** At the end of each era, staking parachain requests AssetsHub to mint new DOT into the sovereign account. These funds are used to pay rewards.
- **Burn inflation**: periodically (at the end of each era), the staking parachain requests a quantity of DOT in the sovereign account to be burned. These funds need to be burn when `HISTORY_DEPTH` eras have passed for some rewards that were not claimed;
- Note: how about transfering the funds to the treasury instead of burning? those funds were already accounted as part of the expected inflation.
### Rewards
- Rewards are tallied on the staking parachain, potentially as derivative tokens (or as points, based on locked DOTs).
- Periodically (or at the user request?), the rewards are synced up with AssetsHub so that they can be used in governance.
- The sync process consists of transfering the minted DOT in the staking sovereign account into the AssetsHub's staker account.
- Sync up requests are batched with XCM.
- Unclaimed rewards (i.e. rewards that haven't been synced up with the assets hub) can be used as fees, in case the free quota has been used up.
- Note: compound rewards will take some time to reflect against the AssetsHub treasury.
### Slashes
- Note: slashed or pre-slashed DOT can still be used in governance.
### Fees
- Option 1 - **Teleporting**:
- Free transaction quota per wallet;
- Teleport DOT to pay for extra transactions once the free quota is used up;
- Possibility to pay for fees with unclaimed rewards to avoid teleporting.
- Option 2 - **Tokenless fees**:
- Free transaction quota per wallet;
- User burns DOT (similar to teleporting); Instead of minting sDOT in StakingHub, keep track of `gas_points`;
- Leverage `SignedExtensions` or other mechanism to ensure the called has enough quota/fees.
- If everything are "points", we can scrap the balances pallet from StakingHub.
### Possible XCM Instructions
- `Stake { AccountId, Token, Vec<AccountId>}`
- Flow: AssetHub -> StakingHub
- All stakers (nomination pools, stakers, etc) have an AccountId associated that can be directly addressed in the XCM message
- Should `Stake` messages stack up or explicitly say the next state the wallet wants?
- `Unstake { AccountId, Token}``
- `Slash { Vec<(AccountId, Token)> }`: batch slash requests per message (note below on batching limits)
- may be a mix of batching slashes and slashes XCM messages:
```
Slash {
Vec<(AccountId, Token)>,
Vec<(AccountId, Token)>,
...,
}
```
```
Batching limits on XCM (currently):
- 64 KB total size the message.
- 100 messages tops.
```
#### Bonding and Nomination
`StakeAsset { asset: MultiAsset, staker: MultiLocation, targets: NominationTarget }`
Flow: `Wallet --StakeAsset--> AssetHub --NoteStaked--> StakingHub`
This instruction should lock/reserve DOT on asset hub, send `NoteStaked` to StakeHub with the `targets`. StakeHub then parses target to either stake in a nomination pool or vec of validators.
NominationTargets could be initially enum with two variants but possibly add more to support other types of staking system:
```rust=
pub enum NominationTargets {
Pool { id: u32 },
Direct { targets: BoundedVec<MultiLocation, ConstU32<8>>}
}
```
#### Unbond
// TODO
`UnstakeAsset { asset: MultiAsset, staker: MultiLocation }`
Flow: `Wallet --UnstakeAsset--> AssetHub --NoteUntaked--> StakingHub`
#### Withdraw
// TODO
`UnlockAsset { asset: MultiAsset, target: MultiLocation }`
### Rewards
// TODO
### Slashing
// TODO
---
## PoV-friedly EPM and Staking pallets
One option is to re-write both EPM and staking pallets from the ground-up with a PoV-friendly design that can leverage all the advantages of parachains.
---
## Other notes
- Assets in AssetHub
- Fees from quota and/or rewards
- Derivate token
- Especially useful for paying fees
- Mint inflation into a sovereign accont on the AssetsHub
- Whenever the unbound DOT amount goes over the locked tokens from an account, the remaining DOT are taken from the sovereign account that keeps the rewards.
- `Stake` and `Unstake` XCM messages to join and leave staking through the AssetsHub
- All the other interactions happen directly with the staking parachain
**Meeting 14/09**
- Every wallet can do the minimal staking actions through the AssetHub: `Stake`, `Unstake`, etc..
- All the different staking parachains may have their own implementations and actions, which need to be performed through the staking parachain.
- However, if the wallet wants to interact directly with the staking parachain, it can directly teleport DOT/token X to the staking parachain.
- However, we need to go through the assets hub due to trust.
- **Slashing**:
- Relay chain sends XCM messages to staking hub
- Those messages will be propagates to AssetHub
- Messages from staking hub -> AssetHub needs to be "batched"
- `Slash { Vec<(AccountId, Token)> }`
- ⭐⭐ What is the minimum viable interface between the AssetHub, staking parachain and relay chain?
- XCM messages through asset hub are monotonic -- the message explicitly says what's the next state of the stake state (or maybe not the best way fwd, actually).
- For staking up state changes, (e.g. stake_extra), it should go through the staking hub since it's implementation specific.
- Batching limits on XCM:
- 64 KB total size the message.
- 100 messages tops.
## TO prepare for the retreat
- https://github.com/paritytech/techretreat-barcamp-upvote/discussions/categories/brainstorms
- Requirements
- Nice to have
- Nominator/validator lifecycle
- High level interface for the relay-chain/staking-parachain/AssetHub
- 2x designs (assets in staking-parachain and/or non-native)
- focus native but with the minimal stakingi integration through AssetHub
- transition from relay-chain -> parachain
- open questions/feedback
----
https://github.com/paritytech/polkadot-sdk/issues/1481
- `pallet-rewards` / `inflation`
- `allocate_era_rewards(AccountId, era)`;
- `slash(AccountId, era)`
- `pallet-slash`
- source of truth for exposures (move from staking)
- historical data from staking would be moved here
- thin `pallet-slash`
- trust the origin (priviledge)
- apply_slash(AccountId)
- Allow SP to reward account (collators) from era points (rather than asking treasury)
1. stepping stone
```
pallet-staking -> pallet-rewards <- *someone_else*
-> pallet-slash <- *someone_else*
```
0. % of the era inflation goes to collator-pot (were the lumpsump is kept)
- send % to lumpsum pot every era
- the budget per era that can be paid to collators per era for a specific parachain
1. treasury votes/somehow defines for lumpsum of rewards to collators per parachain
- tranfered into the pallet-rewards pot (AH, SP, etc ...)
2. somewhere the era points are kept tracker (e.g. pallet-rewards on AH) with permission to exchange era points for DOT allocated by treasury
- (+) Separates concerns