forked from DA0-DA0/dao-contracts
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
DAO rewards distributor emission rate updates (DA0-DA0#848)
- Loading branch information
Showing
13 changed files
with
3,914 additions
and
1,454 deletions.
There are no files selected for viewing
167 changes: 154 additions & 13 deletions
167
contracts/distribution/dao-rewards-distributor/README.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,172 @@ | ||
# DAO Rewards Distributor | ||
|
||
[![dao-rewards-distributor on crates.io](https://img.shields.io/crates/v/dao-rewards-distributor.svg?logo=rust)](https://crates.io/crates/dao-rewards-distributor) | ||
[![dao-rewards-distributor on | ||
crates.io](https://img.shields.io/crates/v/dao-rewards-distributor.svg?logo=rust)](https://crates.io/crates/dao-rewards-distributor) | ||
[![docs.rs](https://img.shields.io/docsrs/dao-rewards-distributor?logo=docsdotrs)](https://docs.rs/dao-rewards-distributor/latest/cw20_stake_external_rewards/) | ||
|
||
The `dao-rewards-distributor` works in conjuction with DAO voting modules to provide rewards over time for DAO members. The contract supports both cw20 and native Cosmos SDK tokens. The following voting power modules are supported: | ||
The `dao-rewards-distributor` works in conjuction with DAO voting modules to | ||
provide rewards streamed over time for DAO members. The contract supports both | ||
native and CW20 Cosmos SDK tokens. Any voting power module that supports the | ||
standard DAO voting module interface is supported for deriving staking reward | ||
allocations, as long it also supports voting power change hooks. This includes, | ||
but is not limited to: | ||
|
||
- `dao-voting-cw4`: for membership or group based DAOs | ||
- `dao-voting-cw20-staked`: for cw20 token based DAOs. | ||
- `dao-voting-cw721-staked`: for NFT based DAOs. | ||
- `dao-voting-token-staked`: for native and Token Factory token based DAOs. | ||
|
||
NOTE: this contract is NOT AUDITED and is _experimental_. USE AT YOUR OWN RISK. | ||
|
||
## Instantiation and Setup | ||
|
||
The contract is instantiated with a number of parameters: | ||
- `owner`: The owner of the contract. Is able to fund the contract and update the reward duration. | ||
- `vp_contract`: A DAO DAO voting power module contract address, used to determine membership in the DAO over time. | ||
- `hook_caller`: An optional contract that is allowed to call voting power change hooks. Often, as in `dao-voting-token-staked` and `dao-voting-cw721-staked` the vp_contract calls hooks for power change events, but sometimes they are separate. For example, the `cw4-group` contract is separate from the `dao-voting-cw4` contract and since the `cw4-group` contract fires the membership change events, it's address would be used as the `hook_caller`. | ||
- `reward_denom`: the denomination of the reward token, can be either a cw20 or native token. | ||
- `reward_duration`: the time period over which rewards are to be paid out in blocks. | ||
The contract is instantiated with a very minimal state. An optional `owner` can | ||
be specified. If it is not, the owner is set to be the address instantiating the | ||
contract. | ||
|
||
### Hooks | ||
|
||
After instantiating the contract it is VITAL to setup the required hooks for it to work. This is because to pay out rewards accurately, this contract needs to know about staking or voting power changes in the DAO. | ||
After instantiating the contract, it is VITAL to set up the required hooks for | ||
it to work. This is because to pay out rewards accurately, this contract needs | ||
to know about staking or voting power changes in the DAO as soon as they happen. | ||
|
||
This can be achieved using the `add_hook` method on contracts that support | ||
voting power changes, such as: | ||
|
||
This can be achieved using the `add_hook` method on contracts that support voting power changes, which are: | ||
- `cw4-group` | ||
- `dao-voting-cw721-staked` | ||
- `dao-voting-token-staked` | ||
- `cw20-stake` | ||
|
||
Finally, the contract needs to be funded with a token matching the denom specified in the `reward_denom` field during instantiation. This can be achieved by calling the `fund` method on the `dao-rewards-distributor` smart contract, and sending along the appropriate funds. | ||
### Creating a new distribution | ||
|
||
Only the `owner` can create new distributions. | ||
|
||
Creating a distribution requires the following configuration: | ||
|
||
- `denom`, which can be a native token or CW20 contract | ||
- `emission_rate`, which determines how the rewards are distributed. there are 3 | ||
options: | ||
- `paused`: no rewards are distributed until the emission rate is updated | ||
- `immediate`: funded rewards are distributed immediately to those with | ||
voting power | ||
- `linear`: `amount` of the denom is distributed to all applicable addresses | ||
per `duration` of time, updating throughout based on changing voting power. | ||
`duration` may be declared in either time (seconds) or blocks. if | ||
`continuous` is true, it will backfill if there are funding gaps using | ||
current voting power. some example configurations may be: | ||
- `1000udenom` per `500 blocks` | ||
- `10udenom` per `24 hours` | ||
- `1udenom` per `1 second` | ||
- `vp_contract` address, which will be used to determine the total and relative | ||
address voting power for allocating the rewards on a pro-rata basis | ||
- `hook_caller` address, which will be authorized to call back into this | ||
contract with any voting power event changes. examples of such events may be: | ||
- user staking tokens | ||
- user unstaking tokens | ||
- user cw-721 state change event | ||
- cw-4 membership change event | ||
- optional `withdraw_destination` address to be used when withdrawing (i.e. | ||
unfunding the remainder of a previously funded distribution). this may be a | ||
subDAO, for example. if not provided, the contract owner is used. | ||
|
||
You can fund a distribution at any point after it's been created, or during | ||
creation if it's for a native token. CW20 tokens must be funded after creation. | ||
Simply including native funds in the create message will suffice. For any token, | ||
you can always top up the funds later, which extends the distribution period. | ||
|
||
### Funding a distribution | ||
|
||
Anyone can fund a distribution once it's been created. | ||
|
||
> **WARNING:** Do not transfer funds directly to the contract. You must use the | ||
> `Fund` execution message in order for the contract to properly recognize and | ||
> distribute the tokens. **Funds will be lost if you don't use the execution | ||
> msg.** | ||
There are a few different emission rates. Below describes the funding behavior | ||
while different emission rates are active. | ||
|
||
#### Linear | ||
|
||
Linear emissions can be continuous or not. | ||
|
||
When a linear emission is **continuous**, it will backfill rewards if there's a gap | ||
between when it finishes distributing everything it's been funded with so far | ||
and when it's funded next. This means that when another funding occurs after a | ||
period of no more rewards being available, it will instantly distribute the | ||
portion of the funds that corresponds with the time that passed in that gap. One | ||
limitation is that it uses the current voting power to backfill. | ||
|
||
When a linear emission is **not continuous**, and a gap in reward funding occurs, it | ||
will simply restart the distribution the next time it receives funding. This may | ||
be less intuitive, but it doesn't suffer from the voting power limitation that | ||
the continuous mode does. | ||
|
||
Upon funding, the start and end are computed based on the funds provided, the | ||
configured emission rate, and whether or not it's set to the continuous mode. If | ||
this is the first funding, or it's not continuous and we're restarting from the | ||
current block, the start block is updated to the current block. The end block is | ||
computed based on the start block and funding duration, calculated from the | ||
emission rate and remaining funds, including any that already existed that have | ||
not yet been distributed. | ||
|
||
Linear emissions can be extended indefinitely by continuously funding them. | ||
|
||
**Example:** if 100_000udenom were funded, and the configured emission rate is | ||
1_000udenom per 100 blocks, we derive that there are 100_000/1_000 = 100 epochs | ||
funded, each of which contain 100 blocks. We therefore funded 10_000 blocks of | ||
rewards. | ||
|
||
#### Immediate | ||
|
||
When set to immediate, funding is immediately distributed based on the voting | ||
power of the block funding occurs on. | ||
|
||
You may fund an immediate distribution as many times as you'd like to distribute | ||
funds instantly to the current members of the DAO. | ||
|
||
#### Paused | ||
|
||
When set to paused, no rewards will be distributed. | ||
|
||
You may fund a paused distribution and accumulate rewards in the contract to be | ||
distributed at a later date, since you can update the emission rate of a | ||
distribution. | ||
|
||
Maybe you want to accumulate rewards in a paused state for a month, and then | ||
distribute them instantly at the end of the month to the DAO. Or maybe you want | ||
to pause an active linear emission, which will hold the funds in the contract | ||
and not distribute any more than have already been distributed. | ||
|
||
### Updating emission rate and other distribution config | ||
|
||
Only the `owner` can update a distribution's config. | ||
|
||
Updating the emission rate preserves all previously distributed rewards and adds | ||
it to a historical value (`historical_earned_puvp`), so updating does not | ||
interfere with users who have not yet claimed their rewards. | ||
|
||
You can also update the `vp_contract`, `hook_caller`, and | ||
`withdraw_destination`. | ||
|
||
> **WARNING:** You probably always want to update `vp_contract` and | ||
> `hook_caller` together. Make sure you know what you're doing. And be sure to | ||
> add/remove hooks on the old and new `hook_caller`s accordingly. | ||
### Withdrawing | ||
|
||
Only the `owner` can withdraw from a distribution. | ||
|
||
This is effectively the inverse of funding a distribution. If the current | ||
distribution is inactive, meaning its emission rate is `paused`, `immediate`, or | ||
`linear` with an expired distribution period (because the end block is in the | ||
past), then there is nothing to withdraw. | ||
|
||
When rewards are being distributed, withdrawing ends the distribution early, | ||
setting the end block to the current one, and clawing back the undistributed | ||
funds to the specified `withdraw_destination`. Pending funds that have already | ||
been distributed, even if not yet claimed, will remain in the contract to be | ||
claimed. Withdrawing only applies to unallocated funds. | ||
|
||
### Claiming | ||
|
||
You can claim funds from a distribution that you have pending rewards for. |
Oops, something went wrong.