mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-26 08:15:19 +00:00
Improve incentive sync documentation (#1417)
This commit is contained in:
parent
1555c99f1b
commit
0bc4547d3c
@ -4,23 +4,85 @@ order: 1
|
|||||||
|
|
||||||
# Concepts
|
# Concepts
|
||||||
|
|
||||||
This module presents an implementation of user incentives that are controlled by governance. When users take a certain action, for example opening a CDP, they become eligible for rewards. Rewards are **opt in** meaning that users must submit a message before the claim deadline to claim their rewards. The goals and background of this module were subject of a previous Kava governance proposal, which can be found [here](https://ipfs.io/ipfs/QmSYedssC3nyQacDJmNcREtgmTPyaMx2JX7RNkMdAVkdkr/user-growth-fund-proposal.pdf)
|
This module implements governance controlled user incentives. When users take a certain action, for example opening a CDP, they become eligible for rewards. Rewards are **opt in** meaning that users must submit a message before the claim deadline to claim their rewards. The goals and background of this module were subject of a previous Kava governance proposal, which can be found [here](https://ipfs.io/ipfs/QmSYedssC3nyQacDJmNcREtgmTPyaMx2JX7RNkMdAVkdkr/user-growth-fund-proposal.pdf)
|
||||||
|
|
||||||
## General Reward Distribution
|
## General Reward Distribution
|
||||||
|
|
||||||
Rewards target various user activity. For example, usdx borrowed from bnb CDPs, btcb supplied to the hard money market, or owned shares in a swap kava/usdx pool.
|
Rewards target various user activity. For example, usdx borrowed from bnb CDPs, btcb supplied to the hard money market, or shares owned in a swap kava/usdx pool.
|
||||||
|
|
||||||
Each second, the rewards accumulate at a rate set in the params, eg 100 ukava per second. These are then distributed to all users ratably based on their percentage involvement in the rewarded activity. For example if a user holds 1% of all funds deposited to the kava/usdx swap pool. They will receive 1% of the total rewards each second.
|
Each second, the rewards accumulate at a rate set in the params, eg 100 ukava per second. These are then distributed to all users ratably based on their percentage involvement in the rewarded activity. For example if a user holds 1% of all funds deposited to the kava/usdx swap pool. They will receive 1% of the total rewards each second.
|
||||||
|
|
||||||
The quantity tracking a user's involvement is referred to as "source shares" in the code. And the total across all users the "total source shares". The quotient then gives their percentage involvement, eg if a user borrowed 10,000 usdx, and there is 100,000 usdx borrowed by all users, then they will get 10% of rewards.
|
The quantity tracking a user's involvement is referred to as "source shares". And the total across all users the "total source shares". The quotient then gives their percentage involvement, eg if a user borrowed 10,000 usdx, and there is 100,000 usdx borrowed by all users, then they will get 10% of rewards.
|
||||||
|
|
||||||
## Efficiency
|
## Efficiency
|
||||||
|
|
||||||
Paying out rewards to every user every block would be slow and lead to long block times. Instead rewards are calculated much less frequently.
|
Paying out rewards to every user every block would be slow and lead to long block times. Instead rewards are calculated lazily only when needed.
|
||||||
|
|
||||||
Every block a global tracker adds up total rewards paid out per unit of user involvement. For example, per unit of xrpb supplied to hard, or per share in a kava/usdx swap pool. A user's specific reward can then be calculated as needed based on their current source shares.
|
First, every block, the amount of rewards to be distributed in that block are divided by the total source shares to get the rewards per share. This is added to a global total (named "global indexes"). This is repeated every block such that the global indexes represents the total rewards a user should be owed per source share if they had held a deposit from when the rewards were created.
|
||||||
|
|
||||||
Users' rewards must be updated whenever their source shares change. This happens through hooks into other modules that run before deposits/borrows/supplies etc.
|
Then, if a user has deposited (say into a CDP) at the very start of the chain (and never changed their deposit), their current reward balance can be calculated at any time $t$ as
|
||||||
|
|
||||||
|
$$
|
||||||
|
\texttt{rewards}_ t = \texttt{globalIndexes}_ t \cdot \texttt{sourceShares}_ t
|
||||||
|
$$
|
||||||
|
|
||||||
|
If a user modifies their source shares (at say time $t-10$) we can still calculate their total rewards:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\texttt{rewards}_ t= \text{rewards accrued up to time t-10} + \text{rewards accrued from time t-10 to time t}
|
||||||
|
$$
|
||||||
|
|
||||||
|
$$
|
||||||
|
\texttt{rewards}_ t = \texttt{globalIndexes}_ {t-10} \cdot \texttt{sourceShares}_ {t-10} + (\texttt{globalIndexes}_ t - \texttt{globalIndexes}_ {t-10}) \cdot \texttt{sourceShares}_ t
|
||||||
|
$$
|
||||||
|
|
||||||
|
This generalizes to any number of source share modifications.
|
||||||
|
|
||||||
|
In code, to avoid storing the entire history of a user's source shares and global index values, rewards are calculated on every source shares change and added to a reward balance:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\texttt{rewards}_ t = \texttt{rewardBalance}_ {t -10} + (\texttt{globalIndexes}_ t - \texttt{globalIndexes}_ {t-10}) \cdot \texttt{sourceShares}_ t
|
||||||
|
$$
|
||||||
|
|
||||||
|
Old values of $\texttt{rewardBalance}$ and $\texttt{globalIndexes}$ ares stored in a `Claim` object for each user as `rewardBalance` and `rewardIndexes` respectively.
|
||||||
|
|
||||||
|
Listeners on external modules fire to update these values when source shares change. For example, when a user deposits to hard, a method in incentive is called. This fundamental operation is called "sync". It calculates the rewards accrued since last time the `sourceShares` changed, adds it to the claim, and stores the current `globalIndexes` in the `rewardIndexes`. Sync must be called whenever source shares change, otherwise incorrect rewards will be distributed.
|
||||||
|
|
||||||
|
Enumeration of 'sync' input states:
|
||||||
|
- `sourceShares`, `globalIndexes`, or `rewardIndexes` should never be negative
|
||||||
|
- `globalIndexes` >= `rewardIndexes` (global indexes must never decrease)
|
||||||
|
- `globalIndexes` and `rewardIndexes` can be positive or 0, where not existing in the store is counted as 0
|
||||||
|
|
||||||
|
- `sourceShares` are the value before the update (eg before a hard deposit)
|
||||||
|
|
||||||
|
| `globalIndexes` | `rewardIndexes` | `sourceShares` | description |
|
||||||
|
|------------------|-----------------|-----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| positive | positive | positive | normal sync |
|
||||||
|
| positive | positive | 0 | normal (can happen when a user is creating a deposit (so shares are increasing from 0)) |
|
||||||
|
| positive | 0 | positive | The claim doesn't hold indexes, so the global indexes must have been added since last sync (eg a new denom was added to reward params). This is indistinguishable from a claim accidentally being deleted, where it will accrue a large amount of rewards. |
|
||||||
|
| positive | 0 | 0 | User is creating source shares. |
|
||||||
|
| 0 | positive | positive | global indexes < claim indexes - fatal error, otherwise the new rewards will be negative |
|
||||||
|
| 0 | positive | 0 | global indexes < claim indexes - fatal error |
|
||||||
|
| 0 | 0 | positive | Source has no rewards yet. User is updating their shares. |
|
||||||
|
| 0 | 0 | 0 | Source has no rewards yet. User is creating source shares. |
|
||||||
|
|
||||||
|
It is important that:
|
||||||
|
- claim indexes are not deleted
|
||||||
|
- Otherwise when sync is called, it will fill them in with 0 values and perform sync as if the user had deposit since the beginning of the rewards (usually accumulating a lot of rewards).
|
||||||
|
- global indexes are not deleted
|
||||||
|
- Otherwise claims cannot be synced. Problematic if a sync happens in a begin blocker and it panics.
|
||||||
|
- hooks are called any time source shares change
|
||||||
|
- If source shares can be updated without a sync, it can be possible to accumulate far too much rewards. For example, a user who holds a small deposit for a long time could deposit a large amount and skip the sync, then trigger a sync which will calculate rewards as if the large deposit was there for a long time.
|
||||||
|
|
||||||
|
The code is further complicated by:
|
||||||
|
- Claim objects contain indexes for several source shares.
|
||||||
|
- Rewards for hard borrows and hard deposits use the same claim object.
|
||||||
|
- Savings and hard hooks trigger any time one in a group of source shares change, but don't identify which changed.
|
||||||
|
- The hard `BeforeXModified` hooks don't show source shares that have increased from zero (eg when a new denom is deposited to an existing deposit). So there is an additional `AfterXModified` hook, and the claim indexes double up as a copy of the borrow/deposit denoms.
|
||||||
|
- The sync operation is split between two methods to try to protect against indexes being deleted.
|
||||||
|
- `InitXRewards` performs a sync assuming source shares are 0, it mostly fires in cases where `sourceShares` = 0 above (except for hard and supply)
|
||||||
|
- `SyncXRewards` performs a sync, but skips it if `globalIndexes` are not found or `rewardIndexes` are not found (only when claim object not found)
|
||||||
|
- Usdx rewards do not support multiple reward denoms.
|
||||||
|
|
||||||
## HARD Token distribution
|
## HARD Token distribution
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user