# Rewards Distribution - Technical Document ## Context The MakerDAO Endgame&reg; cryptoeconomic model features an on-chain mechanism through which users will be able to claim rewards upon staking tokens. This document aims to discuss the design and smart contracts implementation that will enable this capability. ## Early Design Decisions and Constraints For the initial Endgame implementation, we want to reuse existing and battle-tested contracts from Multi-Collateral Dai (MCD) and the broader Ethereum ecosystem. Since time is sensitive, we want to avoid the cost of developing and auditing novel smart contracts and will only do so on a tactical basis. Furthermore, we need to be mindful to keep the complexity of the new protocol modules as low as possible, not only from a development perspective, but also from an operational one, avoiding the need for complex changes in order for the protocol to function as expected. ## The Problem Throughout the several phases of the Endgame implementation, users will be able to farm rewards tokens by staking their assets. The specific tokens and amounts will vary, so we need a general solution that can be reused for this purpose. Reward tokens can be minted on demand or held in custody by a smart contract. We want to avoid having any individuals or group thereof to have control of any amount of assets to make the reward distributions. Furthermore, the execution of distributions themselves should not be under the scrutiny of any individuals or group thereof, allowing anyone to permissionlessly trigger them according to the predefined schedule. ## Proposed Solution The following diagram can help visualize the proposed solution: [![solution-uml-class-diagram](https://hackmd.io/_uploads/rkKWjxUPn.png)](https://hackmd.io/_uploads/rkKWjxUPn.png) **Figure 1:** Simple UML class diagram of the proposed solution. We can leverage [`DssVest`](https://github.com/makerdao/dss-vest/blob/19a9d663bb3a2737f1f0c763365f1dfc6788aad2/src/DssVest.sol) to permissionlessly control the disbursement of the funds given some predefined rules and Synthetix [`StakingRewards`](https://github.com/Synthetixio/synthetix/blob/e311167832c5d5220b7e2c9a67a95be06ab2d1dd/contracts/StakingRewards.sol) to implement the on-chain rewards system. `StakingRewards` is a well known contract in the Ethereum ecosystem, however it uses Solidity 0.5.x -- the latest release of that version is [0.5.15](https://github.com/ethereum/solidity/releases/tag/v0.5.15), and it is almost 4 years old. While this would not be a problem at first, we need some extra features (i.e.: referral code for font-ends). For that reason, we decided to upgrade the contract to Solidity 0.8.x. The contract contains [changes](https://www.diffchecker.com/rgBMl45S/) to make it compatible with the new compiler version, plus the new features. Regarding the referral code feature, we followed the [same strategy implemented in DSR](https://github.com/telome/savings-dai/pull/1/files): - Overload the `stake(uint256 amount)` function as `stake(uint256 amount, uint16 referral)`. - The original behavior is preserved for retro-compatibility. - Front-ends are incentivized to use the new signature to provide their own referral code. - PullUp had some concerns about this feature: > Note that `referral` is not included as an optional parameter to the `withdraw`/`redeem` functions. Doing so would lead to the following issues: > > - Frontends would need to be trusted to correctly pass `referral` to `withdraw`/`redeem` functions. They may have a financial incentive to not do so if rewards are calculated based on how long DAI is deposited for. Worse, they may choose to maliciously pass the `referral` of another frontend in `withdraw`/`redeem` if the reward scheme is based on a pre-defined total promotional budget. > > - Frontends would need to handle the special case of users depositing via frontend A and withdrawing via frontend B which makes frontend implementation more complex and error-prone. > > Instead, we assume that the calculation scheme will keep track of any `Transfer` and `Withdraw` events following a referred `Deposit` for a given token owner. We had the same perception before we started working on it, hence we followed the same principle and `referral` is not present on `withdraw()` neither on `getReward()` nor `exit()`. We also need a component that "glues" `DssVest` and `StakingRewards` contracts together. In Synthetix, the element controlling the disbursement of reward tokens into `StakingRewards` is called [`RewardsDistribution`](https://github.com/Synthetixio/synthetix/blob/bf9d09d9d4d6d4222aaf4501592d602edf9e302d/contracts/RewardsDistribution.sol). This is a permissioned contract which handles distributing reward tokens to multiple `StakingReward` instances. It involves manually setting the distribution amount for each contract and manually sending reward tokens to it. Since our requirements are simpler, we do not want to have the operational burden of the original contract. We implemented a bespoke distribution contract which we call `VestedRewradsDistribution`, which is can permissionlessly distribute rewards on the predefined schedule. `VestedRewardsDistribution` is composed of a `DssVest`, and a `StakingRewards` instances. ### Setup Flow For `NewStable` -> `NewGov` farms: 1. Create a `DssVestMintable` instance. 1. Create a `StakingRewards` instance with `NewStable` as the staking token and `NewGov` as the rewards token. 1. Create a `VestedRewardsDistribution` pointing to `DssVestMintable`, `StakingRewards`. 1. Set the `VestedRewardsDistribution` contract as `rewardsDistribution` for `StakingRewards`. 1. Give the `DssVestMintable` instance above rights to mint `NewGov` tokens.<sup>*</sup> 1. Create a vesting stream on `DssVesteMintable` targeting `VestedRewardsDistribution` with `total = ???` and `duration = 1 year`.<sup>*</sup> 1. Set the `vestID` param in `VestedRewardsDistribution` to the ID of the stream created above.<sup>*</sup> <sup>*</sup> These steps **MUST** be executed through an executive spell. ### Operational Flow Both `SDAO` and `NewGov` farms regular operations are identical: 1. Periodically call `distribute()` on the respective `VestedRewardsDistribution` contract. This can be automated through server-based keeper bots or through decentralized keeper networks. The `distribute` function is permissionless and performs the following actions: 1. Get `maxAmount` the maximum amount to be distributed. 1. Call `vest(vestID, maxAmount)` to pull reward tokens from the vesting stream. 1. Transfer the reward tokens to the respective `StakingRewards` contract. 1. Call `notifyRewardAmount(amount)` on the `StakingRewards` contract. ### Update Flow Updating the rewards distribution is a permissioned flow and must be done through an executive spell. One instance where updates are necessary is when a vesting stream comes to an end or needs to be changed. In that case, the executive spell will re-wire the `VestedRewardsDistribution` contract with a new vesting stream ID: 1. Call `file("vestID", vestID)` on the respective `VestedRwardsDistribution`. ## Different Solutions Explored ### SushiSwap `MasterChefV2` Farm We also considered [`MasterChefV2`](https://etherscan.io/address/0xef0881ec094552b2e128cf945ef17a6752b4ec5d#code) as the main farming smart contract. The main issues with MasterChefV2 contract are: - The contract is a bit overcomplicated. - It has rights to mint tokens. - It can manage multiple staking tokens for the same rewards, which is not required for our use case. For those reasons, we decided to use Synthetix instead. ## Appendix A: Mathematical Model for Distributions The formulas to calculate the exact amount to distribute at a given point in time were derived using some basic calculus primitives. We aimed to have a single source of truth, and it made more sense to express everything in terms of `DssVest` streams because: - `DssVest` is a core MakerDAO contract, has been audited and has been used for a long time without issues. - We want to avoid having to create new or yanking vesting streams because such operations can only be made through executive spells. - `yank` could technically also be done by a trusted party, however it would still mean some operational overhead. ### Overview of `DssVest` A `DssVest` vesting stream is composed of some parameters: - `address usr`: Vesting recipient - `uint48 bgn`: Start of vesting period [timestamp] - `uint48 clf`: The cliff date [timestamp] - `uint48 fin`: End of vesting period [timestamp] - `address mgr`: A manager address that can `yank` (revoke the vesting stream) - `uint8 res`: When `0`, anyone can claim outstanding vested tokes on behalf of `usr`; when `1` only `usr` or an owner can claim. - `uint128 tot`: Total reward amount - `uint128 rxd`: Amount of vested tokens already claimed A vesting stream **always** accumulates at a constant rate. Tokens are not automatically added to the beneficiary `usr` address and need to be pulled through the `vest(uint256 _vestId)` function. [![dss-vest-chart](https://hackmd.io/_uploads/SJm1P9QS2.png)](https://hackmd.io/_uploads/SJm1P9QS2.png) **Figure 3:** A `DssVest` vesting stream over time. Notice how the rate $\rho$ is constant, and the total vested amount is equal to the highlighted area in red. The vesting rate function $\nu(\tau)$ is given by: $$ \nu(\tau) = \rho $$ The total vested amount function $V$ is given by: $$ \begin{align} V &= \int_{0}^{t_f - t_0} \rho \, \mathrm{d}\tau \\ V &= \rho(t_f - t_0) \end{align} $$ Notice we are using the relative axis -- denoted $\tau$ -- instead of the absolute one $t$. For all effects, we can assume that $\tau = t - t_0$. This allows us to express the equations in terms of the duration of the stream instead of absolute timestamps, which could make the formulas quite confusing. ### Constant Rate Distribution A general constant rate distribution is like the following chart: [![constant-dist-chart-1](https://hackmd.io/_uploads/S1A8v57r3.png)](https://hackmd.io/_uploads/S1A8v57r3.png) **Figure 4:** A constant distribution at rate $\kappa$ taking into account the cliff date. The distribution rate function $\delta(\tau)$ is defined by: $$ \delta(\tau) = \begin{cases} 0 & \mbox{when } t \le t_c \\ \kappa & \mbox{when } t_c \lt t \le t_f \\ \end{cases} $$ Notice that the full vested amount would be distributed if and only if: $$ \int_{0}^{t_{f} - t_{0}} \rho \, \mathrm{d}\tau = \int_{0}^{t_{f} - t_{0}} \delta(\tau) \, \mathrm{d}\tau $$ Since $\delta(\tau) = 0$ when $t \le t_c$, we can actually write the equation above like: $$ \int_{0}^{t_{f} - t_{0}} \rho \, \mathrm{d}\tau = \int_{0}^{t_{f} - t_{c}} \kappa \, \mathrm{d}\tau $$ Where: - $\rho$ is the vesting rate. - $\kappa$ is the distribution rate. - $t_{0}$ is the timestamp of the beginning of the vesting stream. - $t_{f}$ is the timestamp of the finish of the vesting stream. - $t_{c}$ is the timestamp of the end of the cliff period. Expanding the integrals above, we would have: $$ \rho(t_f - t_0) = \kappa(t_f - t_c) $$ The left-hand side of the equation is nothing more than the total vested amount, denoted $V$: $$ V = \kappa(t_f - t_c) $$ From that, we can define the distribution rate $\kappa$ as: $$ \kappa = \frac{V}{t_f - t_c} $$ Now we can define the amount distributed $r(t', t)$ at any interval in time $\left] t',t \right]$ can be given by: $$ \begin{align} r(t', t) &= \int_{0}^{t - t_c} \kappa \, \mathrm{d}\tau - \int_{0}^{t' - t_c} \kappa \, \mathrm{d}\tau \\ &= \int_{t'-t_c}^{t-t_c} \kappa \, \mathrm{d}\tau \\ &= \kappa \left( t - t_c \right) - \kappa (t' - t_c) \\ &= \kappa \left( t - t' \right) \\ &= \frac{V\left( t - t' \right)}{t_f - t_c} \end{align} $$ Now we must consider that a nice continuous function can not be implemented in an EVM-based smart contract. We need to convert the function above into a series of discrete distributions that will form a step function: [![constant-distribution-step-function](https://hackmd.io/_uploads/ryPRaimHh.png)](https://hackmd.io/_uploads/ryPRaimHh.png) **Figure 5:** A constant distribution step function, closer to what could be implemented in Solidity. Using a more code-friendly notation, we can call: - $V$: the total amount of tokens distributed `tot`. - $t_{f}$: the timestamp of the end of the vesting stream `fin`. - $t_{c}$: the timestamp of the vesting cliff `clf`. - $t$: the timestamp of the current distribution `when`. - $t'$: the timestamp of the previous distribution `prev`. In pseudocode, we have: ``` r = (now - prev) * tot / (fin - clf) ``` However, during the initial review phase, @hexonaut brought to my attention that if we simply ensure the cliff date is the same as the beginning of the vesting stream, we can safely assume the vesting schedule is equivalent to the constant distribution. [![constant-dist-chart-2](https://hackmd.io/_uploads/SkyxksQSh.png)](https://hackmd.io/_uploads/SkyxksQSh.png) **Figure 6:** A constant distribution matching the vesting stream. If there is a cliff after the beginning of the vesting stream, all tokens accumulated from the beginning of the stream up to the cliff would be immediately available to be disbursed, which is what we do not want. By ensuring `clf == bgn`, we can rewrite the expression above as: ``` r = (now - prev) * tot / (fin - bgn) ``` Notice how `tot / (fin - bgn)` is exactly the vesting distribution rate $\rho$. Therefore, we don't need to explicitly implement it.