Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TOK-403: Document rewards calculations for builders and backers #124

Merged
merged 1 commit into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -374,23 +374,25 @@ Generate coverage report:

### Reward token

TODO:
The reward token for the collective incentives program is `RIF`.

### Staking token

TODO:
The staking token is `stRIF`, where the token balance represents the backing power a backer has to vote for builders.

### Governor

TODO:
The Governor represents the DAO’s governance mechanism and is in charge of the community approval and de-whitelisting of
builders.

### KYC Approver

TODO:
The RoostockCollective Foundation requires builders to got through a KYC process and is in charge of submitting said
approval as well as revoking it if necessary.

### Foundation treasury

TODO:
The RoostockCollective Foundation is in charge of holding and distributing the rewards from the program.

## Acknowledgment

Expand Down
125 changes: 125 additions & 0 deletions docs/RewardsCalculation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Rewards Distribution calculation

The amount of rewards distributed to builders and backers is based on the support throughout time that a builder gets
from backers. The support during a cycle impacts the rewards they receive at the beginning of the next one. Builders get
their rewards ready to claim immediately after the distribution while backers will receive them based on the amount of
time they support the builder during that cycle.

To calculate the amount of rewards to distribute to each builder and its backers, each Gauge keeps track of its rewards
shares. They are calculated based on the amount of votes per time left in the cycle that the gauge has

```text
rewardShares = allocations [wei] * timeUntilNextCycle [seconds]
```

Reward shares get updated every time there is a change in allocations (either adding or removing votes) based on the
time left to end the cycle and after a distribution:

- In case there is an increase in votes, the allocation deviation \* `timeUntilNextCycle` is added to the total rewards
shares
- If votes are removed, then allocation deviation \* `timeUntilNextCycle` is removed from the total reward shares.
- After a distribution, reward shares get reset to the total amount of allocation that the gauge has at the moment
multiplied by the duration of the cycle.

The `BackersManager` keeps track of the total amount of shares of every `Gauge` combined in `totalPotentialReward`.

On the other hand, the `BackersManager` also keeps track of the `rewardsERC20` and `rewardsCoinbase`. They get updated
each time the `BackersManager` receives rewards through `notifyRewardAmount` and after each distribution, when they get
reset to 0.

This way, rewards for each Gauge are calculated based on their `rewardShares` and the `totalPotentialRewards`.

ERC20 rewards:

```text
(rewardShares * rewardsERC20) / totalPotentialReward
```

Coinbase rewards:

```text
(rewardShares * rewardsCoinbase) / totalPotentialReward
```

The `BackersManager` also keeps track of the `backerRewardPercentage`. This is passed to each gauge alongside the
rewards during the distribution so each gauge can calculate how much goes to the builder and how much to the backers.

## Builder Rewards

Their rewards calculation is pretty straightforward: it's based on the `backerRewardPercentage`. For instance, if there
was a distribution to a Gauge of 10 tokens and 10 coinbase with a `backerRewardPercentage` of 40%, the builder would get
6 tokens and 6 coinbase. The rest gets distributed during the cycle among all the backers based on their amount of votes
and time they maintained those votes.

## Backer Rewards

Backers can receive rewards through the distribution and through incentives in reward token and coinbase.

Their rewards are calculated per asset based on a `rewardRate`. What a builder earns in a cycle is in simple terms the
time they have spent supporting the builder multiplied by the reward rate and their allocation rate.

A simplistic way to calculate the rewards assuming there were no changes in allocation or incentives during the cycle
would be:

```text
(timeSinceCycleStarted * rewardRate) * (backerAllocation / totalAllocations)
```

Where the reward rate would be the amount of rewards to distribute per time left in the cycle

```text
rewardRate = totalRewards / timeUntilNextCycle
```

Going into more detail, there is a need to keep track of different variables since the `rewardRate` changes based on the
amount of rewards to distribute, and what the backers receive depends on how their votes change or not.

`rewardRate`: it changes every time rewards for backers are added to the Gauge, either by distribution or by incentives.
It is then affected by the total rewards, where

```text
totalRewards = backersAmount + rewardMissing + leftover
```

- `backersAmount`: newly added rewards, either by incentives or distribution
- `rewardMissing`: rewards from previous cycle that didn't get distributed since that cycle ended without votes or from
current cycle if all allocations were removed at some point
- `leftover`: rewards that haven't been distributed yet in current cycle. It's calculated with previous `rewardRate`
(before updating to the new one) and the time left of the current cycle.

What a backer earns depends directly on the `rewardRate` and the amount of allocations (their own and total
allocations).

So in order to keep track of what was earned until there were either new incentives or new allocations, we have the
`rewardPerTokenStored`. This is the `rewardPerToken` (reward rate per vote) that was earned up to that point. The time
of this update is also stored to be able to calculate future rewards from this point onwards.

- When there is an incentive, the following variables get updated:
- The `rewardRate` (it depends directly on the amount of total rewards)
- The `rewardPerTokenStored` (rewardRate per vote before updating to new rewardRate)
- Time of the update (to current time of the incentive)
- The missing rewards if there are no allocations
- In case of allocating, the following variables get updated:
- The `rewardPerTokenStored` (it depends on the amount of votes)
- Time of the update (to current time of the allocation)
- The Backer rewards: rewards earned up to that point get stored here
- The `backerRewardPerTokenPaid`: this is the `rewardPerTokenStored` at the moment of the allocation. Keeping track of
this is necessary to calculate what a backer can claim at any point of the cycle.

The way we calculate what a backer has earned (and can claim) until the current point in time is the stored rewards +
the rewards since the last update (whether it was an incentives or allocations update).

As it was mentioned before, the stored rewards of a backer get updated every time there is a change in allocation.

```text
backer rewards = previous backer rewards + current rewards
```

Where current rewards are calculated by current allocations of the backer and `rewardPerToken`

```text
currentRewards = backerAllocation * (rewardPerToken - backerRewardPerTokenPaid)
```

The `backerRewardsPerTokenPaid` is taken away from the `rewardPerToken` since it represents what was already included in
the stored backer rewards, we only want to consider what was earned since the last allocation.
12 changes: 7 additions & 5 deletions docs/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -359,23 +359,25 @@ Generate coverage report:

### Reward token

TODO:
The reward token for the collective incentives program is `RIF`.

### Staking token

TODO:
The staking token is `stRIF`, where the token balance represents the backing power a backer has to vote for builders.

### Governor

TODO:
The Governor represents the DAO’s governance mechanism and is in charge of the community approval and de-whitelisting of
builders.

### KYC Approver

TODO:
The RoostockCollective Foundation requires builders to got through a KYC process and is in charge of submitting said
approval as well as revoking it if necessary.

### Foundation treasury

TODO:
The RoostockCollective Foundation is in charge of holding and distributing the rewards from the program.

## Acknowledgment

Expand Down
8 changes: 1 addition & 7 deletions docs/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
- [GaugeRootstockCollective](src/gauge/GaugeRootstockCollective.sol/contract.GaugeRootstockCollective.md)
- [❱ governance](src/governance/README.md)
- [❱ changerTemplates](src/governance/changerTemplates/README.md)
- [WhitelistBuilderChangerTemplateRootstockCollective](src/governance/changerTemplates/WhitelistBuilderChangerTemplateRootstockCollective.sol/contract.WhitelistBuilderChangerTemplateRootstockCollective.md)
- [CommunityApproveBuilderChangerTemplateRootstockCollective](src/governance/changerTemplates/CommunityApproveBuilderChangerTemplateRootstockCollective.sol/contract.CommunityApproveBuilderChangerTemplateRootstockCollective.md)
- [GovernanceManagerRootstockCollective](src/governance/GovernanceManagerRootstockCollective.sol/contract.GovernanceManagerRootstockCollective.md)
- [UpgradeableRootstockCollective](src/governance/UpgradeableRootstockCollective.sol/abstract.UpgradeableRootstockCollective.md)
- [❱ interfaces](src/interfaces/README.md)
Expand All @@ -20,12 +20,6 @@
- [IGovernanceManagerRootstockCollective](src/interfaces/IGovernanceManagerRootstockCollective.sol/interface.IGovernanceManagerRootstockCollective.md)
- [❱ libraries](src/libraries/README.md)
- [UtilsLib](src/libraries/UtilsLib.sol/library.UtilsLib.md)
- [❱ mvp](src/mvp/README.md)
- [ChangeExecutorRootstockCollective](src/mvp/ChangeExecutorRootstockCollective.sol/contract.ChangeExecutorRootstockCollective.md)
- [Governed](src/mvp/Governed.sol/abstract.Governed.md)
- [IChangeExecutorRootstockCollective](src/mvp/IChangeExecutorRootstockCollective.sol/interface.IChangeExecutorRootstockCollective.md)
- [SimplifiedRewardDistributorRootstockCollective](src/mvp/SimplifiedRewardDistributorRootstockCollective.sol/contract.SimplifiedRewardDistributorRootstockCollective.md)
- [Upgradeable](src/mvp/Upgradeable.sol/abstract.Upgradeable.md)
- [BackersManagerRootstockCollective](src/BackersManagerRootstockCollective.sol/contract.BackersManagerRootstockCollective.md)
- [BuilderRegistryRootstockCollective](src/BuilderRegistryRootstockCollective.sol/abstract.BuilderRegistryRootstockCollective.md)
- [CycleTimeKeeperRootstockCollective](src/CycleTimeKeeperRootstockCollective.sol/abstract.CycleTimeKeeperRootstockCollective.md)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# BackersManagerRootstockCollective

[Git Source](https://github.com/RootstockCollective/collective-rewards-sc/blob/0c4368dc418c200f21d2a798619d1dd68234c5c1/src/BackersManagerRootstockCollective.sol)
[Git Source](https://github.com/RootstockCollective/collective-rewards-sc/blob/6d0eca4e2c61e833bcb70c54d8668e5644ba180e/src/BackersManagerRootstockCollective.sol)

**Inherits:**
[ICollectiveRewardsCheckRootstockCollective](/src/interfaces/ICollectiveRewardsCheckRootstockCollective.sol/interface.ICollectiveRewardsCheckRootstockCollective.md),
Expand Down Expand Up @@ -138,6 +138,7 @@ function initialize(
address rewardDistributor_,
uint32 cycleDuration_,
uint24 cycleStartOffset_,
uint32 distributionDuration_,
uint128 rewardPercentageCooldown_
)
external
Expand All @@ -155,6 +156,7 @@ function initialize(
| `rewardDistributor_` | `address` | address of the rewardDistributor contract |
| `cycleDuration_` | `uint32` | Collective Rewards cycle time duration |
| `cycleStartOffset_` | `uint24` | offset to add to the first cycle, used to set an specific day to start the cycles |
| `distributionDuration_` | `uint32` | duration of the distribution window |
| `rewardPercentageCooldown_` | `uint128` | time that must elapse for a new reward percentage from a builder to be applied |

### supportsInterface
Expand All @@ -170,16 +172,19 @@ function supportsInterface(bytes4 interfaceId_) public view override returns (bo

returns true if can withdraw, remaining balance should exceed the current allocation

_user token balance should already account for the update, meaning the check is applied AFTER the withdraw accounting
has become effective._

```solidity
function canWithdraw(address targetAddress_, uint256 value_) external view returns (bool);
function canWithdraw(address targetAddress_, uint256) external view returns (bool);
```

**Parameters**

| Name | Type | Description |
| ---------------- | --------- | ------------------------------------------ |
| `targetAddress_` | `address` | address who wants to withdraw stakingToken |
| `value_` | `uint256` | amount of stakingToken to withdraw |
| Name | Type | Description |
| ---------------- | --------- | ------------------------------------------------------------------------------------------------------------------------ |
| `targetAddress_` | `address` | address who wants to withdraw stakingToken param value\_ amount of stakingToken to withdraw, not used on current version |
| `<none>` | `uint256` | |

### allocate

Expand Down Expand Up @@ -224,7 +229,7 @@ function allocateBatch(

transfers reward tokens from the sender to be distributed to the gauges

_reverts if it is called during the distribution period_
_reverts if it is called during the distribution period reverts if there are no gauges available for the distribution_

```solidity
function notifyRewardAmount(uint256 amount_) external payable notInDistributionPeriod;
Expand Down Expand Up @@ -415,7 +420,7 @@ function _gaugeDistribute(

approves rewardTokens to a given gauge

_give full allowance when it is whitelisted and remove it when it is dewhitelisted_
_give full allowance when it is community approved and remove it when it is dewhitelisted_

```solidity
function _rewardTokenApprove(address gauge_, uint256 value_) internal override;
Expand Down Expand Up @@ -537,3 +542,9 @@ error BeforeDistribution();
```solidity
error PositiveAllocationOnHaltedGauge();
```

### NoGaugesForDistribution

```solidity
error NoGaugesForDistribution();
```
Loading
Loading