|
4 | 4 |
|
5 | 5 | This document specifies the `x/incentive` module of the Umee chain. |
6 | 6 |
|
7 | | -The incentive module allows users to `Bond` collateral `uTokens` from the `x/leverage` module, and governance to create and fund `Incentive Programs` which distribute rewards to users with bonded uTokens over time. Users can `Unbond` tokens over a period of time, after which they can be withdrawn. |
| 7 | +The incentive module allows users to `Bond` collateral `uTokens` from the `x/leverage` module, and governance to create and fund `Incentive Programs` which distribute rewards to users with bonded uTokens over time. |
| 8 | + |
| 9 | +Users can `Unbond` tokens over a period of time, after which they can be withdrawn. `UnbondingDuration` is a module parameter (the same for all tokens) set by governance. Typical values might be `1 day`, `7 days`, or `0 days` (instant unbonding). |
8 | 10 |
|
9 | 11 | The incentive module depends on the `x/leverage` module for information about users' bonded collateral, and also requires that the leverage module prevent bonded or currently unbonding collateral from being withdrawn. |
10 | 12 | There are also a few more advanced interactions, such as instantly unbonding collateral when it is liquidated, and registering an `exponent` when a uToken denom is incentivized for the first time. |
| 13 | + |
| 14 | +## Contents |
| 15 | + |
| 16 | +1. **[Concepts](#concepts)** |
| 17 | + - [Bonding Collateral](#bonding-collateral) |
| 18 | + - [Incentive Programs](#incentive-programs) |
| 19 | + - [Claiming Rewards](#claiming-rewards) |
| 20 | + - [Reward Accumulators](#reward-accumulators) |
| 21 | + - [Reward Trackers](#reward-trackers) |
| 22 | +2. **[State](#state)** |
| 23 | +3. **[Messages](#messages)** |
| 24 | + |
| 25 | +## Concepts |
| 26 | + |
| 27 | +### Bonding Collateral |
| 28 | + |
| 29 | +A user can bond their `x/leverage` collaterized `uTokens` in a `x/incentive` module to receive extra rewards. |
| 30 | + |
| 31 | +Bonding prevents the user from using any `leverage.MsgDecollateralize` or `leverage.MsgWithdraw` which would reduce the user's collateral below the bonded amount. |
| 32 | + |
| 33 | +**Example:** a user has `100 u/UMEE` and `50 u/UMEE` collateral in the leverage module. `40 u/UMEE` from that `50 u/UMEE` is bonded in the incentive module. Their maximum `leverage.MsgDecollateralize` allowed by their bond is `10 u/UMEE` and their maximum `leverage.MsgWithdraw` is `110u/UMEE`. |
| 34 | + |
| 35 | +Bonded collateral is eligible for incentive program rewards as long as it is not currently unbonding. |
| 36 | + |
| 37 | +When the user starts unbonding a uToken, the module's `UnbondingDuration` determines the time after which the tokens are unlocked to the user. |
| 38 | + |
| 39 | +Unbonding uTokens are not eligible for incentive rewards while they unbond, but are still subject to the same restrictions on `leverage.MsgWithdraw` and `leverage.MsgDecollateralize` as bonded tokens. |
| 40 | + |
| 41 | +For example, if a user has `10u/UMEE` bonded and `3u/UMEE` more unbonding, out of a total of `20u/UMEE` collateral, then their current max withdraw is `7u/UMEE`, and they are earning incentive rewards on only `10u/UMEE` uTokens. |
| 42 | + |
| 43 | +The module parameter `MaxUnbondings` limits how many concurrent unbondings a user can have of the same uToken denom, to prevent spam. |
| 44 | + |
| 45 | +Additionally, `MsgEmergencyUnbond` can instantly unbond collateral, starting with in-progress unbondings then bonded tokens. |
| 46 | +This costs a fee - for example, if the parameter `EmergencyUnbondFee` is `0.01`, then 1% of the uTokens unbonded would be donated to the `x/leverage` module reserves while the other 99% are returned to the user. |
| 47 | + |
| 48 | +### Incentive Programs |
| 49 | + |
| 50 | +An `IncentiveProgram` is a fixed-duration program which distributes a predetermined amount of one reward token to users which have bonded selected uTokens during its duration. |
| 51 | + |
| 52 | +For example, the following incentive program would, at each block during the `864000 seconds` after its start at `unix time 1679659746`, distribute a portion of its total `1000 UMEE` rewards to users who have bonded (but are not currently unbonding) `u/uumee` to the incentive module. |
| 53 | + |
| 54 | +```go |
| 55 | +ip := IncentiveProgram { |
| 56 | + StartTime: 1679659746, // Mar 24 2023 |
| 57 | + Duration: 864000, // 10 days |
| 58 | + UToken: "u/uumee", |
| 59 | + TotalRewards: sdk.Coin{"uumee",1000_000000}, |
| 60 | +} |
| 61 | +``` |
| 62 | + |
| 63 | +Reward distribution math is |
| 64 | + |
| 65 | +- **Constant Rate:** Regardless of how much `u/uumee` is bonded to the incentive module at any given block, the program distributes the fraction `blockTime / duration` of its total rewards across users every block it is active (with some corrective rounding). |
| 66 | +- **Weighted by Amount:** A user with `200u/uumee` bonded received twice as many rewards on a given block as a user with `100u/uumee` bonded. |
| 67 | + |
| 68 | +When multiple incentive programs are active simultaneously, they compute their rewards independently. |
| 69 | + |
| 70 | +### Claiming Rewards |
| 71 | + |
| 72 | +A user can claim rewards for all of their bonded uTokens at once using `MsgClaim`. When a user claims rewards, an appropriate amount of tokens are sent from the `x/incentive` module account to their wallet. |
| 73 | + |
| 74 | +There are also times where rewards must be claimed automatically to maintain rewards-tracking math. These times are: |
| 75 | + |
| 76 | +- On `MsgBond` |
| 77 | +- On `MsgBeginUnbonding` |
| 78 | +- When a `leverage.MsgLiquidate` forcefully reduces the user's bonded collateral |
| 79 | + |
| 80 | +Any of the actions above cause the same tokens to be transferred to the user as would have been generated by a `MsgClaim` at the same moment. |
| 81 | + |
| 82 | +By automatically claiming rewards whenever a user's bonded amount changes, the module guarantees the following invariant: |
| 83 | + |
| 84 | +> Between any two consecutive reward claims by an account associated with a specific bonded uToken denom, the amount of bonded `uTokens` of the given denom for that account remained constant. |
| 85 | +
|
| 86 | +### Reward Accumulators |
| 87 | + |
| 88 | +The incentive module must calculate rewards each block without iterating over accounts and past incentive programs. It does so by storing a number of `RewardAccumulator` |
| 89 | + |
| 90 | +```go |
| 91 | +ra := RewardAccumulator{ |
| 92 | + denom: "u/uumee", |
| 93 | + exponent: 6, |
| 94 | + rewards: "0.00023uumee, 0.00014ibc/1234", |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +The example reward accumulator above can be interpreted as: |
| 99 | + |
| 100 | +> If `10^6` `u/uumee` was bonded at genesis and had remained bonded since, it would have accumulated `0.00023uumee` and `0.00014ibc/1234` in rewards. |
| 101 | +
|
| 102 | +The incentive module must store one `RewardAccumulator` for each uToken denom that is currently (or has been previously) incentivized. |
| 103 | + |
| 104 | +During `EndBlock` when rewards are being distributed by incentive programs, the module divides the amount of tokens to distribute by the current `TotalBonded` which it stores for each uToken denom, to increment the `rewards` field in each `RewardAccumulator`. |
| 105 | + |
| 106 | +The `exponent` field is based on the exponent of the base asset associated with the uToken in `denom`. It reduces the loss of precision that may result from dividing the (relatively small) amount of rewards distributed in a single block by the (relatively large) total amount of uTokens bonded. |
| 107 | + |
| 108 | +Note that the `rewards` field must never decrease, nor can any nonzero `RewardAccumulator` be deleted, even after associated incentive programs have ended. The `exponent` field should also never be changed. |
| 109 | + |
| 110 | +### Reward Trackers |
| 111 | + |
| 112 | +`RewardTracker` is stored per-user, and is used in combination with `RewardAccumulator` to calculate rewards since the user's last claim. |
| 113 | + |
| 114 | +```go |
| 115 | +rt := RewardTracker{ |
| 116 | + account: "umee1s84d29zk3k20xk9f0hvczkax90l9t94g72n6wm", |
| 117 | + denom: "u/uumee", |
| 118 | + rewards: "0.00020uumee, 0.00014ibc/1234", |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +The example reward tracker above can be interpreted as: |
| 123 | + |
| 124 | +> The last time account `umee1s84d29zk3k20xk9f0hvczkax90l9t94g72n6wm` claimed rewards for bonded `u/uumee`, the value of `RewardAccumulator` (not tracker) for that denom was `"0.00020uumee, 0.00014ibc/1234"`. Therefore, the rewards to claim this time should be based on the _increase_ since then. |
| 125 | +
|
| 126 | +Because the amount of bonded uTokens for this user was constant between the previous `RewardTracker` increase and the current moment, the following simple calculation determines rewards: |
| 127 | + |
| 128 | +> rewards to claim = (RewardAccumulator - RewardTracker) * (AmoundBonded / 10^Exponent) |
| 129 | +
|
| 130 | +Whenever an account claims rewards (including the times when rewards are claimed automatically due to `MsgBond`, `MsgBeginUnbonding`, or `leverage.MsgLiquidate`), the account's `RewardTracker` of the affected denom must be updated to the current value of `RewardAccumulator`. |
| 131 | + |
| 132 | +A `RewardTracker` can also be deleted from the module's store once an account's bonded amount has reached zero for a uToken `denom`. The tracker will be initialized to the accumulator's current value if the account decides to bond again later. |
| 133 | + |
| 134 | +## State |
| 135 | + |
| 136 | +The incentive module stores the following in its KVStore and genesis state: |
| 137 | + |
| 138 | +- `Params` |
| 139 | +- Every upcoming, ongoing, and completed `IncentiveProgram` |
| 140 | +- The next available program `ID` (an integer) |
| 141 | +- The last unix time rewards were calculated |
| 142 | +- `RewardAccumulator` of every bonded uToken denom, unless zero |
| 143 | +- `RewardTracker` for each user associated with nonzero bonded uTokens with a nonzero a reward accumulator above |
| 144 | +- All nonzero bonded uTokens amounts for each account (excldes unbondings) |
| 145 | +- All unbondings in progress for each account |
| 146 | + |
| 147 | +Additionally, some mathematically redundant information is maintained in `KVStore` but not genesis state for efficient operations: |
| 148 | + |
| 149 | +- `TotalBonded` for every bonded uToken denom (total excludes unbondings). |
| 150 | +- `TotalUnbonding` for every bonded uToken denom. |
| 151 | +- `AmountUnbonding` for each account with one or more unbondings in progress. |
| 152 | + |
| 153 | +These totals are kept in sync with the values they track by the functions in `keeper/store.go`, which update the totals whenever any of the values they reference are changed for any reason (including when importing genesis state). |
| 154 | + |
| 155 | +## Messages |
| 156 | + |
| 157 | +See [proto](https://github.com/umee-network/umee/blob/main/proto/umee/incentive/v1/tx.proto) for detailed fields. |
| 158 | + |
| 159 | +Permissionless: |
| 160 | + |
| 161 | +- `MsgClaim` Claims any pending rewards for all bonded uTokens |
| 162 | +- `MsgBond` Bonds uToken collateral (and claims pending rewarda) |
| 163 | +- `MsgBeginUnbonding` Starts unbonding uToken collateral (and claims pending rewards) |
| 164 | +- `MsgEmergencyUnbond` Instantly unbonds uToken collateral for a fee based on amount unbonded (and claims pending rewards) |
| 165 | +- `MsgSponsor` Funds an entire incentive program with rewards, if it has been passed by governance but not yet funded. |
| 166 | + |
| 167 | +Governance controlled: |
| 168 | + |
| 169 | +- `MsgGovSetParams` Sets module parameters |
| 170 | +- `MsgGovCreatePrograms` Creates one or more incentive programs. These programs can be set for community funding or manual funding in the proposal. |
0 commit comments