Try   HackMD

Allowing Opengov participation for Nomination Pool members

If you are a nomination pool member, you may already know that when you join a NominationPool, your funds are transferred from your account to the pooled account (in other words, you lose the ownership of these funds). We can call this Transfer and Stake strategy. In return, you are assigned some points which you use later to claim your funds.

A side effect of this is, you cannot use your funds in the pool for governance.

We had been working to fix this issue by keeping the funds held in member account itself and giving the right to stake it to the pool account (see #3905). We call this Delegate and Stake strategy.

Current status

This is already live on Westend (testnet).

The audit identified couple of follow-up issues that I am currently addressing and expect it to be merged very soon. Once done, these changes could go live as soon as in early August on Polkadot/Kusama.

Developer Guide

Reading pool member's balance

Use the runtime api NominationPoolsApi_member_total_balance that takes AccountId as input and returns Balance.

Reading pool's balance

Use the runtime api NominationPoolsApi_pool_balance that takes PoolId as input and returns Balance.

Checking migration status of the pool member

Use the runtime api NominationPoolsApi_member_needs_delegate_migration that takes AccountId as input, and returns true if user is not migrated yet.

Apply pending slash to the member

You can find more information about this in the lazy slashes section.

Check if a user has pending slash by using the runtime api NominationPoolsApi_member_pending_slash that returns a Balance. If the returned value is greater than 0, the extrinsic nominationPools.apply_slash(memberAccount) can be dispatched. The origin is rewarded with part of the slash based on the slash reward configuration of the runtime.

Migrating a member

Following conditions should pass for a member to be migrated:

  • Ensure either member's free balance or member's balance in the pool (NominationPoolsApi_member_total_balance) is above ED.
  • Ensure the member is not a direct staker as well. One way to do it is to check if member exists as key in the storage item Bonded in pallet-staking.

Next, check if user needs to be migrated with the runtime api NominationPoolsApi_member_needs_delegate_migration. If it returns true, the extrinsic nominationPools.migrateDelegation(memberAccount) can be dispatched. If successful, any transaction fee would be refunded.

User Migration Guide

In most cases, a pool member don't have to do anything. Their funds would be migrated automatically by a bot script via a permissionless extrinsic. However there are cases where your migration can fail and they might need to resolve those issues before migrating.

Cases where migration would fail:

  • If the user participates in the pool but also stake directly. Affects 646 (1.4% of pool members) accounts on Polkadot.
  • If the user's free balance is zero and their active contribution in the pool is lower than the existential deposit. This can happen only if there was a slash to the pool. Affects 165 accounts on Kusama, None on Polkadot.

Dual staking

Unfortunately, because of the way funds are locked, a direct staker would not be able to use the pools anymore. More details here.

To be able to migrate your pool funds, following are the steps I recommend:

  • Unbond all your funds from staking.
  • Withdraw all your funds from staking after waiting for the unbonding period (7 days for Kusama, 28 days for Polkadot).
  • Migrate your funds.

The first two steps can already be taken to avoid the migration failure when these changes are deployed on Polkadot and Kusama.

Zero balance and lower than ED contribution

if your balance is zero and your pool contribution is below ED in the pool, you will need to top up your account before you can migrate your pool funds.

Migrating via polkadot.js.org

// TODO Integrate a way to migrate in staking dashboard and document that as well.

  • A pool member account can be migrated using the permissionless extrinsic nominationPools.migrateDelegation(memberAccount). The fee is refunded if transaction is successful.

image

A detailed summary of changes for those who are curious

High level changes

After migration, the pool account becomes a virtual staking account. What that means is, they do not stake with their own funds, but gets delegation from other accounts.

There is a new pallet into the runtime pallet-delegated-staking that manages all the delegation.

Nomination pools uses pallet-delegated-staking to switch from

  • the older strategy of members transferring funds to pool account which is staked in pallet-staking
  • to the new way where pool members delegate funds to the pool account, and pool accounts acts as an agent of these funds to stake in pallet-staking as a virtual staking account.

// TODO: try using as less new terms as possible.

Lazy Slashes

Currently whenever there is a slashing event, pool members affected are slashed eagerly. Since the funds are only in the pool account, only one account needs to be slashed per pool. With the new DelegateStake feature, pool funds are spread all its members and each member need to be slashed proportionally. This would be an unbounded operation therefore not feasible.

Instead, with DelegateStake, we apply slashes to pool members lazily. Internally, we keep account of how much a pool and its members needs to be slashed. There is a permissionless extrinsic to apply any pending slash to the pool member. If successful, the caller gets part of slash as reward (if configured), incentivising free market bots to monitor and apply slashes swiftly.

The advantage is, the slashing (computation) cost is spread over multiple blocks.

A disadvantage is that a member might have more funds in their account locked than their actual pool contribution, which they can use to vote, until these pending slashes are applied.

There is a runtime api to help with checking if a user has any pending slash NominationPoolsApi_member_pending_slash.

Migration

Migration happens in two stages:

  1. Pool is migrated. This will happen as part of a runtime upgrade.
  2. Individual pool members are migrated. This will be done through a permissionless extrinisic.

Pool migration

The following steps is what happens when a pool is migrated

  • The pool account is marked as Virtual Stakers in pallet-staking. See storage item VirtualStakers.
  • It is registered as an Agent in pallet-delegation-staking. The pallet keeps track of total funds delegated, and any pending slashes to the pool account, among other things.
  • All pool funds are moved to ProxyDelegator, a new pot account of pallet-delegated-staking. ProxyDelegator delegates back to pool account and is later used by pool members to migrate their funds into their own accounts.

Member migration

Members can migrate their funds in the pool (that temporarily exists in ProxyDelegator). The funds are transferred to the member account with a hold.

Double staking issue

pallet-staking uses the deprecated currency locks which can overlap with holds used by the pools after migration. This unfortunately means
normal stakers (both nominators and validators) on pallet-staking won't be able to bond their funds in the pool. If we don't disable this, an account could potentially stake the same fund twice.

For existing accounts who are both in pool and are normal stakers would need to withdraw all their funds from staking in order to migrate their delegation.

In future, we will migrate staking funds to use the same holds mechanism that is used by the pools and re-enable accounts to do both. In the meanwhile, if a user wants to do both, I suggest to use/create a separate account and use that for directly staking.