Skip to main content

Reward Streams

Reward Streams is a powerful and flexible implementation of the billion-dollar algorithm, a popular method for proportional reward distribution in the Ethereum developer community. This project extends the algorithm's functionality to support both staking and staking-free (based on balance changes tracking) reward distribution, multiple reward tokens, and permissionless registration of reward distribution schemes (reward streams). This makes Reward Streams a versatile tool for incentivizing token staking and holding in a variety of use cases.

Reward Streams was developed to address the limitations of the billion-dollar algorithm, and to provide a more flexible and powerful implementation. Here's what Reward Streams offers:

  1. A common base contract (BaseRewardStreams) that is reused by both staking and balance-tracking mechanisms of rewards distribution.
  2. An easy-to-use mechanism for balance-tracking reward distribution, which requires only a subtle change to the ERC-20 token contract.
  3. A permissionless mechanism to create a reward stream, enabling anyone to incentivize staking/holding of any token with any reward.
  4. The ability for users to earn up to 5 different reward tokens simultaneously for staking/holding of a single rewarded token.
  5. Additive, fixed length epoch-based distribution of rewards where the reward rate may differ from epoch to epoch.
  6. Protection against reward tokens being lost in case nobody earns them.

How Does It Work?

Reward Streams operates in two modes of rewards distribution: staking and balance-tracking. Each mode has a separate contract implementation.

Reward Streams Mechanism

Tracking Reward Distribution

The balance-tracking TrackingRewardStreams implementation inherits from the BaseRewardStreams contract. It defines the IBalanceTracker.balanceTrackerHook function, which is required to be called on every transfer of the rewarded token if a user opted in for the hook to be called. Provided that the account opted in for their balance to be tracked, on every balance change of the account which is a result of the deposit, withdrawal, shares transfer etc., the credit vault contract calls the balanceTrackerHook function. Once the hook is called, the contract updates the account's balance and all the enabled rewards distribution data in order to track the rewards accrual.

The already deployed TrackingRewardStreams (referred to as BalanceTracker in the deployed contract addresses section) can be used by any other newly developed ERC20-like contract. Assuming that one wants to embed the reward distribution mechanism into their own contract, it is enough that the token contract calls the balanceTrackerHook function on every balance change of the account. For inspiration, one can take a look at the mock ERC20 token contract here.

Staking Reward Distribution

The staking StakingRewardStreams implementation also inherits from the BaseRewardStreams contract. It defines two functions: stake and unstake, which are used to stake and unstake the rewarded token. This contract has been audited but haven't been yet deployed. If you are interested in using it, please reach out to us so we can deploy it for you.

Internal Mechanics

In both modes, each distributor contract defines an EPOCH_DURATION constant, which is the duration of a single epoch. This duration cannot be less than 1 week and more than 10 weeks. The currently deployed TrackingRewardStreams instance used by the EVK vaults has an epoch duration of 2 weeks.

When registering a new reward stream for the rewarded token, one must specify the startEpoch number when the new stream will come into effect. To protect users from obvious mistakes, the distributor contract enforces a soft requirement that ensures the startEpoch is not more than 5 epochs into the future. Moreover, one must specify the rewardAmounts array, which instructs the contract how much reward one wants to distribute in each epoch starting from startEpoch. The rewardAmounts array must have a length of at most 25 for one function call.

If rewarded epochs of multiple reward streams overlap, the amounts will be combined and the effective distribution will be the sum of the amounts in the overlapping epochs.

Permissionless Reward

Unlike other permissioned distributors based on the billion-dollar algorithm, Reward Streams distributors do not have an owner or admin meaning that none of the assets can be directly recovered from them. This property is required in order for the system to work in a permissionless manner, allowing anyone to transfer rewards token to a distributor and register a new reward stream. The drawback of this approach is that reward tokens may get lost if nobody earns them at the given moment (i.e. nobody stakes required assets or nobody enabled earning those rewards). In order to prevent reward tokens from being lost when nobody earns them at the moment, the rewards get virtually accrued by address(0) and, in exchange for updating given distribution data, are claimable by anyone with use of updateReward function.

Known Limitations

  1. Epoch duration may not be shorter than 1 week and longer than 10 weeks: This limitation is in place to ensure the stability and efficiency of the distribution system. The longer the epoch, the more gas efficient the distribution is.
  2. Registered reward stream can start at most 5 epochs ahead and can last for a maximum of 25 epochs: This limitation ensures that user inputs are reasonable and helps protect them from making obvious mistakes.
  3. A user may have at most 5 rewards enabled at a time for a given rewarded token: This limitation is in place to prevent users from enabling an excessive number of rewards, which could lead to increased gas costs and potential system instability.
  4. During its lifetime, a distributor may distribute at most type(uint160).max / 2e19 units of a reward token per rewarded token: This limitation is in place not to allow accumulator overflow.
  5. Not all rewarded-reward token pairs may be compatible with the distributor: This limitation may occur due to unfortunate rounding errors during internal calculations, which can result in registered rewards being irrevocably lost. To avoid this, one must ensure that the following condition, based on an empirical formula, holds true:

6e6 * block_time_sec * expected_apr_percent * 10**reward_token_decimals * price_of_rewarded_token / 10**rewarded_token_decimals / price_of_reward_token > 1

For example, for the SHIB-USDC rewarded-reward pair, the above condition will not be met, even with an unusually high assumed APR of 1000%: block_time_sec = 12 expected_apr_percent = 1000 rewarded_token_decimals = 18 reward_token_decimals = 6 price_of_rewarded_token = 0.00002 price_of_reward_token = 1

6e6 * 12 * 1000 * 10**6 * 0.00002 / 10**18 / 1 is less than 1.

  1. If nobody earns rewards at the moment (i.e. nobody staked/deposited yet), they're being virtually accrued by address(0) and may be claimed by anyone: This feature is designed to prevent reward tokens from being lost when nobody earns them at the moment. However, it also means that unclaimed rewards could potentially be claimed by anyone.
  2. If nobody earns rewards at the moment, despite being virtually accrued by address(0) and claimable by anyone, they might still get lost due to rounding: This limitation may occur due to unfortunate rounding errors during internal calculations, which can result in registered rewards being irrevocably lost. To ensure that the value lost due to rounding is not significant, one must ensure that 1 wei of the reward token multiplied by epoch duration has negligible value.

For example, if the epoch duration is 2 weeks (which corresponds to ~1.2e6 seconds) and the reward token is WBTC, in one rounding, up to ~1.2e6 WBTC may be lost. At the current BTC price, this value corresponds to ~$700, which is a significant value to lose for just one update of the reward stream. Hence, one must either avoid adding rewards that have a significant value of 1 wei or make sure that someone earns rewards at all times.

  1. Distributor contracts do not have an owner or admin meaning that none of the assets can be directly recovered from them: This feature is required for the system to work in a permissionless manner. However, it also means that if a mistake is made in the distribution of rewards, the assets cannot be directly recovered from the distributor contracts.
  2. Distributor contracts do not support rebasing and fee-on-transfer tokens: This limitation is in place due to internal accounting system limitations. Neither reward nor rewarded tokens may be rebasing or fee-on-transfer tokens.
  3. Precision loss may lead to the portion of rewards being lost to the distributor: Precision loss is inherent to calculations in Solidity due to its use of fixed-point arithmetic. In some configurations of the distributor and streams, depending on the accumulator update frequency, a dust portion of the rewards registered might get irrevocably lost to the distributor. However, the amount lost should be negligible as long as the condition from 5. is met.
  4. Permissionless nature of the distributor may lead to DOS for popular reward-rewarded token pairs: The distributor allows anyone to incentivize any token with any reward. A bad actor may grief the party willing to legitimately incentivize a given reward-rewarded token pair by registering a tiny reward stream long before the party decides to register the reward stream themselves. Such a reward stream, if not updated frequently, may lead to a situation where the legitimate party is forced to update it themselves since the time the bad actor set up their stream. This may be costly in terms of gas.