Staking
While the below scheme is different from traditional checkpointed or snapshotted staking protocols, it has the benefit being more efficient while consuming far less state. Specifically the state is constant in the number of operators and nominators. Most of the time, we only adjust the current_epoch_fees
for an operator pool every time fees from confirmed blocks are collected (which is frequent).
Individual nominator shares for a given pool are independent of other nominators shares. Nominators shares only have to be updated when a deposit or withdrawal is made, which are relatively infrequent (compared to bundle production).
Operator Registration
Any user who has the MinOperatorStake
and sufficient hardware for running an operator node may choose to register as an operator by calling the register_operator
extrinsic on a specific domain.
Operator Config
The settings for nominators, which include:
nomination_tax
: the tax rate for compute fees earned by the pool (e.g. 5%).minimum_nominator_stake
: the minimum stake needed to participate as a nominator for this pool. Should be > 1 SSC.
Steps
- The operator must provide an
operator_config
and the following fields:domain_id
of the target domain they wish to stake onsigning_key
- This immediately creates a new entry in the
Operators
registry, which creates a new staking pool for nominators to join. The pool is divided into shares on pro-rata basis, proportional to each nominators amount in thecurrent_total_stake
. - The deposit is applied to the
Deposits
storage for this operator as a pending deposit. Theoperator_id
is also added to thenext_operators
set in theDomainRegistry
. - A 20% of the deposit is transferred to a
storage_fund_account
towards paying storage fees for bundles. The rest of the deposit amount remains locked in operator’s balance account. - At the next epoch transition for this domain, the
current_total_stake
for this domain is updated to reflect the operator’s deposit (minus the storage fund %). Theoperator_id
is moved to thecurrent_operators
for this domain. - The operator will get shares equal to their deposit (the
share_price
at the pool start is equal to1
). Thetotal_shares
andcurrent_total_stake
are set to the staked value. Their initial pending deposit is moved toKnownDeposit
. - The operator may now participate in the VRF bundle election.
Nominator Registration
Any user who has MinNominatorStake
may choose to join this operator’s pool by calling the nominate_operator
extrinsic with the deposit_amount
of SSC they wish to stake.
Note: the actual calculations are done in Shannons, and 1 SSC = Shannons
First nomination:
- The deposit of
deposit_amount
SSC is added to theDeposits
storage as aPendingDeposit
for the operator pool they chose. - A 20% of the deposit is transferred to a
storage_fund_account
towards paying storage fees for bundles. The rest of the deposit amount remains locked in operator’s balance account. - Deposit
deposit_amount
(minus the 20% for storage fund) is locked fornominator_id
account inpallet_balances
andoperator_id
’sdeposits_in_epoch
is incremented by the same amount. - During the next epoch transition, the
deposit_amount
is added tocurrent_total_stake
of the operator pool and domain’scurrent_total_stake
.
Note that nominator shares are not yet “assigned” until they either add a new deposit in a subsequent epoch or initiate a withdrawal.
For subsequent nomination deposits see Stake Deposits.
Stake Withdrawals
Any registered operator or nominator may initiate a withdrawal of their stake from the pool by submitting a withdraw_stake
extrinsic which support different types of withdrawal:
- All, withdraw all the stake
- Percent, withdraw a given percentage of the stake
- Stake, withdraw a given amount of stake (i.e. balance) The stake is calculated by the share price at this instant, it may not be accurate and may withdraw a bit more stake if there is reward happen later in this epoch
- Share, withdraw a given amount of share
If the nominator's stake after withdrawal results in an amount below the operator's required minimum_nominator_stake,
the nominator is automatically completely unstaked. Contrary, the operator cannot withdraw below the MinOperatorStake
.
A withdrawal is logically composed of 2 parts. First, a user request to withdraw shares (withdraw_stake
) that unstakes a given amount of stake at the end of epoch, and second, a request to unlock (unlock_funds
extrinsic) and actually transfer to the balance account the amount of SSC for those shares after the locking period has passed.
A nominator can submit withdraw_stake
extrinsics to request withdrawal. Withdrawals requested in the same epoch are aggregated into one, and there are at most WithdrawalLimit
number of withdrawal allowed for a given nominator at the same time, once this limit is reached, the nominator need to submit unlock_funds
extrinsic to unlock the withdrawal before requesting a new one.
- Nominator submits a
withdraw_stake
extrinsic. - If there are any pending deposits
PendingDeposit
for this nominator for any previous epochn
, then use theOperatorEpochSharePrice
stored for those specific deposit epochn
and calculate theshares
and add it to existingshares
inKnownDeposit
.This would not be unbounded and at max be 1PendingDeposit
for a given nominator. - If there are any withdrawals
withdrawals_in_shares
for this nominator for any previous epochn
, then use theOperatorEpochSharePrice
stored for those specific epochn
and convert theshares
to SSC, move the withdrawal into the convertedwithdrawals
vector and add the amount tototal_withdrawal_amount
. - Ensure the total number of withdrawals in the
withdrawals
vector is less thanWithdrawalLimit
- Once the pending nominator deposit shares are calculated and added to known shares,
- If there are no withdrawals initiated this epoch yet, a
Withdrawal
item is created withallowed_since_domain_epoch
set to current epoch,unlock_at_confirmed_domain_block_number = LatestConfirmedDomainBlockNumber(domain_id) + StakeWithdrawalLockingPeriod
,withdraw_shares
requested,storage_fee_refund = withdraw_shares/total_shares*storage_fee_deposit/total_storage_fee_deposit*storage_fund_account_balance
- If there is already a withdrawal for the same
allowed_since_domain_epoch
,Withdrawal
storage is incremented with intended #withdraw_shares
andstorage_fee_refund
to withdraw, andunlock_at_confirmed_domain_block_number
is updated to a higher valueLatestConfirmedDomainBlockNumber(domain_id) + StakeWithdrawalLockingPeriod
. All the withdrawals submitted in the same epoch are unlocked at the unlocking time of the last withdrawal of that epoch. - Similar to
total_withdrawal_amount
, addstorage_fee_refund
tototal_storage_fee_withdrawal
Nominator
shares and storage fee deposit in their known depositsKnownDeposit
are reduced:shares = shares - withdraw_shares
storage_fee_deposit = storage_fee_deposit(1 - withdraw_shares/shares)
- If there are no withdrawals initiated this epoch yet, a
operator_id
‘swithdrawals_in_epoch
are incremented with total ofwithdraw_shares
from all nominators withdraw requests for the next epoch.- The total of all nominator storage fee deposits (
total_storage_fee_deposit
) is decremented bystorage_fee_refund
. - The
storage_fee_refund
amount is locked in the operator’sstorage_fund_account
and not used to pay for any future bundles.
Unlock withdrawn funds
Once the previous epoch (of withdraw_shares
extrinsic) is completed and share price for that epoch is noted, nominators can withdraw the requested shares that had passed the unlocking period.
- Nominator submits
unlock_funds(operator_id, nominator_id)
- Operator iterate the
nominator_id
's withdrawal from the oldest to newest- If the withdrawal's
StakeWithdrawalLockingPeriod
period is not complete (theunlock_at_confirmed_domain_block_number
is higher thanLatestConfirmedDomainBlockNumber(domain_id)
), then stop iterating. - Remove the withdrawal from the the
withdrawals
vector - Calculate
amount_to_unlock
as SSC amount for withdrawn shares using share value for epoch stored atOperatorEpochSharePrice
the withdraw is allowed from. - Add the
amount_to_unlock
tototal_amount_to_unlock
and thestorage_fee_refund
tototal_storage_fee_refund
- If the withdrawal's
- Deduct
total_amount_to_unlock
fromtotal_withdrawal_amount
andtotal_storage_fee_refund
fromtotal_storage_fee_withdrawal
- Release
total_storage_fee_refund
from locked to transferrable state for nominator account. - If
total_amount_to_unlock
is more than SSC amount that was locked for staking on this operator, the excess is minted and locked to nominator account. - Finally,
total_amount_to_unlock
of SSC is released from locked to transferrable state for nominator account.
Stake Deposits
Existing nominators may choose to add more stake to the same operator’s pool they are already nominating using the same nominate_operator
extrinsic with the deposit_amount
of SSC they wish to stake.
- Nominator with balance account
nominator_id
submits an extrinsic to deposit for next epoch for a givenOperatorPool
asnominate_operator(OperatorPoolId, nominator_id, deposit_amount)
- The
deposit_amount
is locked fornominator_id
account inpallet_balances
. - The deposit is added as
PendingDeposit
toDeposits
storage for this nominator-operator pair as a key. - If there is a pending deposit for this nominator submitted this epoch, the existing pending deposit is incremented with
deposit_amount
. - If there are any pending deposits for this nominator for any previous epoch
n
, then use theOperatorEpochSharePrice
stored for those specific deposit epochn
and calculate theshares
and add it to existingshares
inKnownDeposit
.This would not be unbounded and at max be 1PendingDeposit
for a given nominator. - A 20% of the deposit is transferred to a
storage_fund_account
towards paying storage fees for bundles. The rest of the deposit amount remains locked in nominator’s balance account. - The amount transferred is applied to domain’s balance.
- The deposit of
deposit_amount
(minus the storage fund %) SSC is applied to thedeposits_in_epoch
table within the operator pool. - During the next epoch transition,
- Compute the operator’s pool end-of-epoch
share_price
as the sum of all stake in the pool and rewards gained during the previous epoch divided by the total number of shares(current_total_stake + current_epoch_rewards * (1-nomination_tax)) / total_shares
. - Assign the
shares
to this nominator based on theshare_price
of the pool (asshares = deposit_amount / share_price
). - The
deposit_amount
is added tocurrent_total_stake
of the operator pool and domain’scurrent_total_stake
. - The
shares
of this nominator are added tototal_shares
of the pool.
- Compute the operator’s pool end-of-epoch
Operator Deregistration
See the corresponding deregister_operator
extrinsic.
Slashing Stake
If any submit_fraud_proof
extrinsic is accepted by the chain, the operator’s entire pool is slashed.
- The pool is immediately frozen for withdrawals and deposits, by setting
status
inOperators
registry entry of this operator toSlashed
. - All new deposits are canceled and returned to senders.
- The
Withdrawals
table is checked to see if there are any withdrawals initiated for this operator, all of which are sent to the treasury account. - The entire operator pool balance of
current_total_stake
and any rewards received will be transferred to treasury account and operator is removed from thenext_operators
. - At epoch transition, the entire pool balance is applied to the SSC treasury account, and the Operator is removed from the
Operators
registry.
Fees Distribution
For each confirmed domain block this domain, fees are distributed as follows:
- The storage fees (
ER::block_fees.storage_fee
) for bundles in that block are refunded to the operators who authored those bundles according to how much they front-paid for bundle size. - The execution fees (
ER::block_fees.execution_fee
) from the newly confirmed domain block are applied to thecurrent_epoch_fees
for this domain in theDomainRegistry
and the operators pool (who submitted this ER) in theOperators
registry. - Operator will get a cut of all fees collected by this pool as per
nomination_tax
specified in operator’s config at the epoch transition. - Operator’s cut will be automatically re-staked (via a deposit) to the operator’s nomination at next epoch transition. Operator’s
shares
,total_shares
andcurrent_total_stake
will be updated with the corresponding deposit. - At the next epoch transition we check all domains and apply all changes corresponding to rewards, deposits and withdrawals to the
current_total_stake
, and the iterating through all registered operators and updating theircurrent_total_stake
as well. Note that this only changes the total pool balance, but does not affecttotal_shares
orshares
for any individual nominators.
Note: because of the challenge period, the fees distribution is delayed, and as a result, the set of nominators in the operator pool may have changed in the meantime. New operators who joined after the newly confirmed block was produced will get fees share from the blocks produced before they joined. Nominators who withdraw some shares will get lesser fee share. Nominators who withdraw completely, do not get fees for the last blocks still in challenge period, even though they were staking when those blocks were produced.