Staking
Overview
This pallet is very similar to the Reward pallet - it is also based on the Scalable Reward Distribution algorithm. The reward pallet keeps track of how much rewards vaults have earned. However, when nomination is enabled, there needs to be a way to relay parts of the vault’s rewards to its nominators. Furthermore, the nominator’s collaterals can be consumed, e.g., when a redeem is cancelled. This pallet is responsible for both tracking the rewards, and the current amount of contributed collaterals of vaults and nominators.
The idea is to have one reward pool per vault, where both the vault and all of its nominators have a stake equal to their contributed collateral. However, when collateral is consumed, either in cancelRedeem or liquidateVault, the collateral of each of these stakeholders should decrease proportionally to their stake. To be able to achieve this without iteration, in addition to tracking RewardPerToken
, a similar value SlashPerToken
is introduced. Similarly, in addition to RewardTally
, we now also maintain a SlashTally
is for each stakeholder. When calculating a reward for a stakeholder, a compensated stake is calculated, based on Stake
, SlashPerToken
and SlashTally
.
When a vault opts out of nomination, all nominators should receive their collateral back. This is achieved by distributing all funds from the vault’s shared collateral as rewards. However, a vault is free to opt back into nominator after having opted out. It is possible for the vault to do this before all nominators have withdrawn their reward. To ensure that the bookkeeping remains intact for the nominators to get their rewards at a later point, all variables are additionally indexed by a nonce, which increases every time a vault opts out of nomination. Effectively, this create a new pool for every nominated period.
Note
Most of the functions in this pallet that have a _at_index
also have a version without this suffix that does not take a nonce
argument, and instead uses the value stored in Nonce. For brevity, these functions without the suffix are omitted in this specification.
Data Model
Maps
TotalStake
Maps (currencyId, nonce, vaultId)
to the total stake deposited by the given vault and its nominators, with the given nonce and currencyId.
TotalCurrentStake
Maps (currencyId, nonce, vaultId)
to the total stake deposited by the given vault and its nominators, with the given nonce and currencyId, excluding stake that has been slashed.
TotalRewards
Maps (currencyId, nonce, vaultId)
to the total rewards distributed to the vault and its nominators. This value is currently only used for testing purposes.
RewardPerToken
Maps (currencyId, nonce, vaultId)
to the amount of reward the vault and its nominators get per unit of stake.
RewardTally
Maps (currencyId, nonce, vaultId, nominatorId)
to the reward tally the given nominator has for the given vault’s reward pool, in the given nonce and currency. The tally influences how much the nominator can withdraw.
Stake
Maps (currencyId, nonce, vaultId, nominatorId)
to the stake the given nominator has in the given vault’s reward pool, in the given nonce and currency. Initially, the stake is equal to its contributed collateral. However, after a slashing has occurred, the nominator’s collateral must be compensated, using computeStakeAtIndex.
SlashPerToken
Akin to RewardPerToken: maps (currencyId, nonce, vaultId)
to the amount the vault and its nominators got slashed for per unit of stake. It is used for computing the effective stake (or equivalently, its collateral) in computeStakeAtIndex.
SlashTally
Akin to RewardTally: maps (currencyId, nonce, vaultId, nominatorId)
to the slash tally the given nominator has for the given vault’s reward pool, in the given nonce and currency. It is used for computing the effective stake (or equivalently, its collateral) in computeStakeAtIndex.
Nonce
Maps (currencyId, vaultId)
current value of the nonce the given vault uses in the given currency. The nonce is increased every time forceRefund is called, i.e., when a vault opts out of nomination. Since nominators get their collateral back as a withdrawable reward, the bookkeeping must remain intact when the vault once again opts into nomination. By incrementing this nonce, effectively a new reward pool is created for the new session. All externally callable functions use the nonce stored in this map, except for the reward withdrawal function withdrawRewardAtIndex.
Functions
depositStake
Adds a stake for the given account and currency in the reward pool.
Specification
Function Signature
depositStake(currencyId, vaultId, nominatorId, amount)
Parameters
currencyId
: The currency for which to add the stakevaultId
: Account of the vaultnominatorId
: Account of the nominatoramount
: The amount by which the stake is to increase
Events
Postconditions
Stake[currencyId, nonce, vaultId, nominatorId]
MUST increase byamount
TotalStake[currencyId, nonce, vaultId]
MUST increase byamount
TotalCurrentStake[currencyId, nonce, vaultId]
MUST increase byamount
RewardTally[currencyId, nonce, vaultId, nominatorId]
MUST increase byRewardPerToken[currencyId, nonce, vaultId] * amount
.SlashTally[currencyId, nonce, vaultId, nominatorId]
MUST increase bySlashPerToken[currencyId, nonce, vaultId] * amount
.
withdrawStake
Withdraws the given amount stake for the given nominator or vault. This function also modifies the nominator’s SlashTally
and Stake
, such that the Stake
is once again equal to its collateral.
Specification
Function Signature
withdrawStake(currencyId, vaultId, nominatorId, amount)
Parameters
currencyId
: The currency for which to add the stakevaultId
: Account of the vaultnominatorId
: Account of the nominatoramount
: The amount by which the stake is to decrease
Events
Preconditions
Let
nonce
beNonce[currencyId, vaultId]
, andLet
stake
beStake[nonce, currencyId, vaultId, nominatorId]
, andLet
slashPerToken
beSlashPerToken[currencyId, nonce, vaultId]
, andLet
slashTally
beslashTally[nonce, currencyId, vaultId, nominatorId]
, andLet
toSlash
bestake * slashPerToken - slashTally
Then:
stake - toSlash
MUST be greater than or equal toamount
Postconditions
Let
nonce
beNonce[currencyId, vaultId]
, andLet
stake
beStake[nonce, currencyId, vaultId, nominatorId]
, andLet
slashPerToken
beSlashPerToken[currencyId, nonce, vaultId]
, andLet
slashTally
beslashTally[nonce, currencyId, vaultId, nominatorId]
, andLet
toSlash
bestake * slashPerToken - slashTally
Then:
Stake[currencyId, nonce, vaultId, nominatorId]
MUST decrease bytoSlash + amount
TotalStake[currencyId, nonce, vaultId]
MUST decrease bytoSlash + amount
TotalCurrentStake[currencyId, nonce, vaultId]
MUST decrease byamount
SlashTally[nonce, currencyId, vaultId, nominatorId]
MUST be set to(stake - toSlash - amount) * slashPerToken
RewardTally[nonce, currencyId, vaultId, nominatorId]
MUST decrease byrewardPerToken * amount
slashStake
Slashes a vault’s stake in the given currency in the reward pool. Conceptually, this decreases the stakes, and thus the collaterals, of all of the vault’s stakeholders. Indeed, computeStakeAtIndex will reflect the stake changes on the stakeholder.
Specification
Function Signature
slashStake(currencyId, vaultId, amount)
Parameters
currencyId
: The currency for which to add the stakevaultId
: Account of the vaultamount
: The amount by which the stake is to decrease
Preconditions
TotalStake[currencyId, Nonce[currencyId, vaultId], vaultId]
MUST NOT be zero
Postconditions
Let nonce
be Nonce[currencyId, vaultId]
, and initialTotalStake
be TotalCurrentStake[currencyId, nonce, vaultId]
. Then:
SlashPerToken[currencyId, nonce, vaultId]
MUST increase byamount / TotalStake[currencyId, nonce, vaultId]
TotalCurrentStake[currencyId, nonce, vaultId]
MUST decrease byamount
if
initialTotalStake - amount
is NOT zero,RewardPerToken[currencyId, nonce, vaultId]
MUST increase byRewardPerToken[currencyId, nonce, vaultId] * amount / (initialTotalStake - amount)
computeStakeAtIndex
Computes a vault’s stakeholder’s effective stake. This is also the amount collateral that belongs to the stakeholder.
Specification
Function Signature
computeStakeAtIndex(nonce, currencyId, vaultId, amount)
Parameters
nonce
: The nonce to compute the stake atcurrencyId
: The currency for which to compute the stakevaultId
: Account of the vaultnominatorId
: Account of the nominator
Postconditions
Let stake
be Stake[nonce, currencyId, vaultId, nominatorId]
, and
Let slashPerToken
be SlashPerToken[currencyId, nonce, vaultId]
, and
Let slashTally
be slashTally[nonce, currencyId, vaultId, nominatorId]
, then
The function MUST return
stake - stake * slash_per_token + slash_tally
.
distributeReward
Distributes rewards to the vault’s stakeholders.
Specification
Function Signature
distributeReward(currencyId, reward)
Parameters
currencyId
: The currency being distributedvaultId
: the vault for which distribute rewardsreward
: The amount being distributed
Events
Postconditions
Let nonce
be Nonce[currencyId, vaultId]
, and
Let initialTotalCurrentStake
be TotalCurrentStake[currencyId, nonce, vaultId]
, then:
If
initialTotalCurrentStake
is zero, or ifreward
is zero, then:The function MUST return zero.
Otherwise (if
initialTotalCurrentStake
andreward
are not zero), then:RewardPerToken[currencyId, nonce, vaultId)]
MUST increase byreward / initialTotalCurrentStake
TotalRewards[currencyId, nonce, vaultId]
MUST increase byreward
The function MUST return
reward
.
computeRewardAtIndex
Calculates the amount of rewards the vault’s stakeholder can withdraw.
Specification
Function Signature
computeRewardAtIndex(nonce, currencyId, vaultId, amount)
Parameters
nonce
: The nonce to compute the stake atcurrencyId
: The currency for which to compute the stakevaultId
: Account of the vaultnominatorId
: Account of the nominator
Postconditions
Let stake
be the result of computeStakeAtIndex(nonce, currencyId, vaultId, nominatorId)
, then:
Let rewardPerToken
be RewardPerToken[currencyId, nonce, vaultId]
, and
Let rewardTally
be rewardTally[nonce, currencyId, vaultId, nominatorId]
, then
The function MUST return
max(0, stake * rewardPerToken - reward_tally)
withdrawRewardAtIndex
Withdraws the rewards the given vault’s stakeholder has accumulated.
Specification
Function Signature
withdrawRewardAtIndex(currencyId, vaultId, amount)
Parameters
nonce
: The nonce to compute the stake atcurrencyId
: The currency for which to compute the stakevaultId
: Account of the vaultnominatorId
: Account of the nominator
Events
Preconditions
computeRewardAtIndex(nonce, currencyId, vaultId, nominatorId)
MUST NOT return an error
Postconditions
Let reward
be the result of computeRewardAtIndex(nonce, currencyId, vaultId, nominatorId)
, then:
Let stake
be Stake(nonce, currencyId, vaultId, nominatorId)
, then:
Let rewardPerToken
be RewardPerToken[currencyId, nonce, vaultId]
, and
TotalRewards[currency_id, nonce, vault_id]
MUST decrease byreward
RewardTally[currencyId, nonce, vaultId, nominatorId]
MUST be set tostake * rewardPerToken
The function MUST return
reward
forceRefund
This is called when the vault opts out of nomination. All collateral is distributed among the stakeholders, after which the vault withdraws his part immediately.
Specification
Function Signature
forceRefund(currencyId, vaultId)
Parameters
currencyId
: The currency for which to compute the stakevaultId
: Account of the vault
Events
Preconditions
Let nonce
be Nonce[currencyId, vaultId]
, then:
distributeReward(currencyId, vaultId, TotalCurrentStake[currencyId, nonce, vaultId])
MUST NOT return an errorwithdrawRewardAtIndex(nonce, currencyId, vaultId, vaultId)
MUST NOT return an errordepositStake(currencyId, vaultId, vaultId, reward)
MUST NOT return an errorNonce[currencyId, vaultId]
MUST be increased by 1
Postconditions
Let nonce
be Nonce[currencyId, vaultId]
, then:
distributeReward(currencyId, vaultId, TotalCurrentStake[currencyId, nonce, vaultId])
MUST have been calledwithdrawRewardAtIndex(nonce, currencyId, vaultId, vaultId)
MUST have been calledNonce[currencyId, vaultId]
MUST be increased by 1depositStake(currencyId, vaultId, vaultId, reward)
MUST have been called AFTER having increased the nonce
DepositStake
Event Signature
DepositStake(currencyId, vaultId, nominatorId, amount)
Parameters
currencyId
: The currency of the reward poolvaultId
: Account of the vaultnominatorId
: Account of the nominatoramount
: The amount by which the stake is to increase
Functions
WithdrawStake
Event Signature
WithdrawStake(currencyId, vaultId, nominatorId, amount)
Parameters
currencyId
: The currency of the reward poolvaultId
: Account of the vaultnominatorId
: Account of the nominatoramount
: The amount by which the stake is to increase
Functions
DistributeReward
Event Signature
DistributeReward(currencyId, vaultId, amount)
Parameters
currencyId
: The currency of the reward poolvaultId
: Account of the vaultamount
: The amount by which the stake is to increase
Functions
WithdrawReward
Event Signature
WithdrawReward(currencyId, vaultId, nominatorId, amount)
Parameters
currencyId
: The currency of the reward poolvaultId
: Account of the vaultnominatorId
: Account of the nominatoramount
: The amount by which the stake is to increase
Functions
ForceRefund
Event Signature
ForceRefund(currencyId, vaultId)
Parameters
currencyId
: The currency of the reward poolvaultId
: Account of the vault
Functions
IncreaseNonce
Event Signature
IncreaseNonce(currencyId, vaultId, nominatorId, amount)
Parameters
currencyId
: The currency of the reward poolvaultId
: Account of the vaultamount
: The amount by which the stake is to increase
Functions