---
# System prepended metadata

title: Distributing rewards on-chain

---

# Distributing rewards on-chain


There's a vault containing a single asset (want) deposited by multiple users and these pooled funds are used to earn a revenue stream (rewards) through some mechanism. What's the best way to efficiently distribute this revenue back to the depositors?

#### Off-chain

1. Collect all the rewards to a central contract
2. Compute the distribution by iterating over all depositors
3. Do a merkle airdrop to all the depositors

This is possible because off-chain computation is cheap. The disadvantage of this approach is that it is centralized and requires a trusted enitity.

#### On-chain

Obviously the same thing can't be done on-chain since iterating over all users and generating a merkle tree is infeasible because of gas costs. You need to come up with something more efficient.

### Case I: Single harvest locking vault
Let's start with a simple vault. The vault only accepts deposits before a given `startTime` and only allows withdraws after it has been harvested i.e. the funds stay locked within the harvest period.

```solidity
uint256 constant PRECISION = 1e18;

// Simplified
contract Vault is ERC20,
    // ... 
{
    ERC20 public immutable token; // want
    ERC20 public immutable reward;
    
    uint256 epochStartTime;
    bool harvested;
    
    // ...

    // Deposit
    function _mint(address _to, uint256 _amount) internal override {
        // Before epoch has started
        require(block.timestamp < epochStartTime);

        // ...
    }

    // Withdraw
    function _burn(address _from, uint256 _amount) internal override {
        // After harvest
        require(harvested);

        // ...
    }

    function _transfer(
        address _from,
        address _to,
        uint256 _amount
    ) internal override {
        // Before epoch start or after harvest
        require(block.timestamp < epochStartTime || harvested);

        // ...
    }
    
    // ...
    
    function harvest() public onlyAuth {
        require(block.timestamp >= epochStartTime && !harvested);
  
        // Set storage
        harvested = true;
        
        // Harvest rewards
        // ...
    }

    function claimReward() public {                                   
        // ...
    }

}
```

The shares of user are $s_u$ and total shares are $S$. Both of these remain constant over the epoch.

![](https://i.imgur.com/QOYN132.png)

After harvest, the reward of user $u$ is simply
$$
\begin{align*}
R_{u} &= \frac{s_{u}}{S}\cdot \Delta r\\
      &= s_{u}\cdot \frac{\Delta r}{S}\\
\end{align*}
$$


```solidity
// Simplified
contract Vault is ERC20,
    // ... 
{
     // ...
    
    uint256 rewardPerShare;
    // user -> bool
    mapping(address => bool) claimed;
    
    function harvest() public onlyAuth {
        require(block.timestamp >= epochStartTime && !harvested);
  
        // Set storage
        harvested = true;
        
        // Claim rewards
        uint256 harvestedReward;
        // ...

        rewardPerShare = (harvestedReward * PRECISION) / totalSupply();
        
        // ...
    }

    function claimReward() public {
        uint256 rewardAmount = (balanceOf(msg.sender) * rewardPerShare) /
            PRECISION;

        claimed[msg.sender] = true;
                                   
        reward.transfer(msg.sender, rewardAmount);
                                   
        // ...
    }
}
```

If the reward token is the same as the vault token, then $\frac{\Delta r}{S}$ represents the `pricePerShare`. In this case, rewards can simply be distributed by donating to the vault and increasing the `pricePerShare` instead of keeping a track of it separately.

The same methtod can easily be extended to multiple rewards by converting `reward` into a `rewards[]` array. 

### Case II: Flash-loan provider

We now consider a vault that allows anyone to flashloan the assets in exchange of a fee which is taken in `reward` token. Anyone can deposit/withdraw into the vault at all times. 

```solidity
// Simplified
contract Vault is ERC20,
    // ... 
{
    ERC20 public immutable token; // want
    ERC20 public immutable reward;
    
    // ...

    // Deposit
    function _mint(address _to, uint256 _amount) internal override {
        // ...
    }

    // Withdraw
    function _burn(address _from, uint256 _amount) internal override {
        // ...
    }

    function _transfer(
        address _from,
        address _to,
        uint256 _amount
    ) internal override {
        // ...
    }
  
    // ...
    
    function flashLoan(uint256 _amount, bytes memory _calldata) public {
        uint256 tokenBefore = token.balanceOf(address(this));
        uint256 rewardBefore = reward.balanceOf(address(this));
        
        uint256 expectedFee;
        // ...
        
        token.transfer(msg.sender, _amount);
        IFlashLoanReceiver(msg.sender).callback(_calldata);
    
        uint256 tokenAfter = token.balanceOf(address(this));
        uint256 rewardAfter = reward.balanceOf(address(this));
        
        uint256 rewardEarned = rewardAfter - rewardBefore;
        
        require(tokenAfter >= tokenBefore);
        require(rewardEarned >= expectedFee);
          
        // ...
    }

    function claimReward() public {                                   
        // ...
    }
}
```

User shares change over time and reward is accumulated from multiple steps $n$ instead of one. 

![](https://i.imgur.com/HL9zW0k.png)

One thing to note is that the reward distribution from a flashloan is based only on a user's shares at the time of the flashloan.

For a user $u$, the reward accumulated between steps $n_1$ and $n_2$ 

$$
\Delta R_{u}(n_1, n_2) = \sum_{n_1}^{n_2} \frac{s_{un}}{S_n}\cdot \Delta r_n
$$

Defining $R_{u}(n) = \sum_{n} \frac{s_{un}}{S_n}\cdot \Delta r_n$, this expression can be rewritten as

$$
\begin{align*}
R_{u}(n_2) &= R_{u}(n_1) + \Delta R_u(n_1, n_2)\\
R_{u}(0) &= 0
\end{align*}
$$


Assume user shares $s_{un}$ remain constant between $n_1$ and $n_2$

$$
\begin{align*}
\Delta R_{u}(n_1, n_2) &= \sum_{n_1}^{n_2} \frac{s_{un}}{S_n}\cdot \Delta r_n\\
                       &= s_{un_2}\cdot\sum_{n_1}^{n_2} \frac{\Delta r_n}{S_n}\\
                       &= s_{un_2}\cdot\left[\sum_{n=0}^{n_2} \frac{\Delta r_n}{S_n} - \sum_{n=0}^{n_1} \frac{\Delta r_n}{S_n}\right]\\
                       &= s_{un_2}\cdot \left[A(n_2) - A(n_1)\right]
\end{align*}
$$
where $A(n) = \sum_{n=0}^{n} \frac{\Delta r_n}{S_n}$. 

Given $A(n)$, it is easy to calculate the reward for any user. At every step $n$ when a flashloan is taken, you update $A(n)$ by adding $\frac{\Delta r_n}{S_n}$ to the old  value. The total rewards of the user after $n_2$ steps would be

$$
R_{u}(n_2) = R_u(n_1) + s_{un_2}\cdot \left[A(n_2) - A(n_1)\right]
$$

When user's share do change, you evaluate the $R_u(n_1)$ value and store it for the next use.

```solidity
// Simplified
contract Vault is ERC20,
    // ... 
{
    // ...
    
    uint256 rewardPerShare;
    
    struct UserRewardInfo {
        uint256 pendingReward;
        uint256 rewardPerShareOld;
    }
    // user -> struct
    mapping(address => UserRewardInfo) userRewardInfo;
    
    // ...

    // Deposit
    function _mint(address _to, uint256 _amount) internal override {
        _updateReward(_to);
        // ...
    }

    // Withdraw
    function _burn(address _from, uint256 _amount) internal override {
        _updateReward(_from);
        // ...
    }

    function _transfer(
        address _from,
        address _to,
        uint256 _amount
    ) internal override {
        _updateReward(_from);
        _updateReward(_to);
        // ...
    }
  
    function flashLoan(uint256 _amount, bytes memory _calldata) public {   
        // ...
        
        rewardPerShare += rewardEarned * PRECISION / totalSupply();
        
        // ...
    }

    function claimReward() public {
        _updateReward(msg.sender);
        
        UserRewardInfo storage info = userRewardInfo[_account];
        
        uint256 pendingReward = info.pendingReward;
        info.pendingReward = 0;
        
        reward.transfer(msg.sender, pendingReward);
           
        // ...
    }
    
    function _updateReward(address _account) internal {
        UserRewardInfo storage info = userRewardInfo[_account];
        
        info.pendingReward +=
            (balanceOf(_account) * (rewardPerShare - info.rewardPerShareOld)) /
            PRECISION;
        info.rewardPerShareOld = rewardPerShare;
    }
}
```

### Case III: Reward streamer

The next vault we consider is one where rewards are streamed over time to the depositors at a known rate. An example of this would be a tokenzied version of SNX's [StakingRewards](https://github.com/Synthetixio/synthetix/blob/develop/contracts/StakingRewards.sol), tokenized Sushi [MasterChef](https://github.com/pancakeswap/pancake-farm/blob/master/contracts/MasterChef.sol) or a tokenzied Curve [Gauge](https://github.com/curvefi/curve-dao-contracts/blob/master/contracts/gauges/LiquidityGaugeV5.vy).

![](https://i.imgur.com/s5Ub2Iv.png)


```solidity
// Simplified
contract Vault is ERC20,
    // ... 
{
    ERC20 public immutable token; // want
    ERC20 public immutable reward;
    
    // ...

    // Deposit
    function _mint(address _to, uint256 _amount) internal override {
        // ...
    }

    // Withdraw
    function _burn(address _from, uint256 _amount) internal override {
        // ...
    }

    function _transfer(
        address _from,
        address _to,
        uint256 _amount
    ) internal override {
        // ...
    }
  
    function claimReward() public {                                   
        // ...
    }
}
```

Since the reward rate is known, new reward between any consecutive deposits/withdrawals can be easily calculated. Therefore, we can think of this as Case II but where the deposit/withdrawals and the reward generating events are the same.

![](https://i.imgur.com/uIs1SAY.png)

The reward streamed at the step $(n-1) \to n$ can be defined as
$$
\Delta r_n = \int_{t_{n-1}}^{t_{n}} r'(t) dt
$$

Like in Case II, take steps $n_1$ and $n_2$ between which a user's shares $s_{un}$ remain constant. The reward earned by thte user during this period is:

$$
\Delta R_{u}(n_1, n_2) = s_{un_2}\cdot \left[A(n_2) - A(n_1)\right]
$$

where $A(n) = \sum_{n=0}^{n} \frac{\Delta r_n}{S_n}$. 

And the total rewards earned are:
$$
R_{u}(n_2) = R_u(n_1) + s_{un_2}\cdot \left[A(n_2) - A(n_1)\right]
$$

```solidity
// Simplified
contract Vault is ERC20,
    // ... 
{
    // ...
    
    uint256 rewardPerShare;
    
    struct UserRewardInfo {
        uint256 pendingReward;
        uint256 rewardPerShareOld;
    }
    // user -> struct
    mapping(address => UserRewardInfo) userRewardInfo;
    
    // dr/dt
    uint256 rewardRate; // in PRECISION
    uint256 lastUpdateTimestamp;
    
    // ...

    // Deposit
    function _mint(address _to, uint256 _amount) internal override {
        _updateRewardPerShare();
        _updateReward(_to);
        // ...
    }

    // Withdraw
    function _burn(address _from, uint256 _amount) internal override {
        _updateRewardPerShare();
        _updateReward(_from);
        // ...
    }

    function _transfer(
        address _from,
        address _to,
        uint256 _amount
    ) internal override {
        _updateRewardPerShare();
        _updateReward(_from);
        _updateReward(_to);
        // ...
    }
    
    function claimReward() public {
        _updateRewardPerShare();
        _updateReward(msg.sender);
        
        UserRewardInfo storage info = userRewardInfo[_account];
        
        uint256 pendingReward = info.pendingReward;
        info.pendingReward = 0;
        
        reward.transfer(msg.sender, pendingReward);
           
        // ...
    }
    
    function _updateReward(address _account) internal {        
        UserRewardInfo storage info = userRewardInfo[_account];
        
        info.pendingReward +=
            (balanceOf(_account) * (rewardPerShare - info.rewardPerShareOld)) /
            PRECISION;
        info.rewardPerShareOld = rewardPerShare;
    }
    
    function _updateRewardPerShare() internal {
        rewardPerShare +=
            _getReward(lastUpdateTimestamp, block.timestamp) /
            totalSupply();
        lastUpdateTimestamp = block.timestamp;
    }
    
    function _getReward(uint256 t1, uint256 t2) internal view returns (uint256) {
        return rewardRate * (t2 - t1);
    }
}
```



### Case IV: Badger's harvesting vaults

Finally, we have Badger vaults where rewards are generated by harvesting yields using the deposited funds. The big difference between this and the three cases above is that the total reward generated by a user's funds is not known at the time of a deposit/withdrawal since it depends on a future harvest.

```solidity
// Simplified
contract Vault is ERC20,
    // ... 
{
    ERC20 public immutable token; // want
    ERC20 public immutable reward;
    
    // ...

    // Deposit
    function _mint(address _to, uint256 _amount) internal override {
        // ...
    }

    // Withdraw
    function _burn(address _from, uint256 _amount) internal override {
        // ...
    }

    function _transfer(
        address _from,
        address _to,
        uint256 _amount
    ) internal override {
        // ...
    }
  
    // ...
    
    function harvest() public {
        // ...
    }

    function claimReward() public {                                   
        // ...
    }
}
```

If you naively distribute rewards by taking the shares at the time of harvest like Case II, then anyone can frontrun the harvest and take a disproportionate cut of the rewards. Not fair. 

![](https://i.imgur.com/SLOW3GQ.png)


### Wat do?

####  1. Fuck it

Distribute rewards based on shares at the time of harvests.
Examples - Current badger, yearn and most autocompounding vaults.

```solidity
// Simplified
contract Vault is ERC20,
    // ... 
{
    // ...
    
    uint256 rewardPerShare;
    
    struct UserRewardInfo {
        uint256 pendingReward;
        uint256 rewardPerShareOld;
    }
    // user -> struct
    mapping(address => UserRewardInfo) userRewardInfo;
    
    // ...

    // Deposit
    function _mint(address _to, uint256 _amount) internal override {
        _updateReward(_to);
        // ...
    }

    // Withdraw
    function _burn(address _from, uint256 _amount) internal override {
        _updateReward(_from);
        // ...
    }

    function _transfer(
        address _from,
        address _to,
        uint256 _amount
    ) internal override {
        _updateReward(_from);
        _updateReward(_to);
        // ...
    }
   
    // ...
    
    function harvest() public {
        // ...
        
        rewardPerShare += rewardEarned * PRECISION / totalSupply();
        
        // ...
    }

    function claimReward() public {
        _updateReward(msg.sender);
        
        UserRewardInfo storage info = userRewardInfo[_account];
        
        uint256 pendingReward = info.pendingReward;
        info.pendingReward = 0;
        
        reward.transfer(msg.sender, pendingReward);
           
        // ...
    }
    
    function _updateReward(address _account) internal {
        UserRewardInfo storage info = userRewardInfo[_account];
        
        info.pendingReward +=
            (balanceOf(_account) * (rewardPerShare - info.rewardPerShareOld)) /
            PRECISION;
        info.rewardPerShareOld = rewardPerShare;
    }
}
```

##### Pros
- Easy to implement
- Fixed gas cost for deposits/withdrawals

##### Cons
- Unfair

#### 2. Suck it

Harvest at every deposit/withdrawal.
Example - A contrived example is a tokenized Curve Gauge.

```solidity
// Simplified
contract Vault is ERC20,
    // ... 
{
    // ...
    
    struct UserRewardInfo {
        uint256 pendingReward;
        uint256 rewardPerShareOld;
    }
    
    uint256 rewardPerShare;
    // user -> struct
    mapping(address => UserRewardInfo) userRewardInfo;
    
    // ...

    // Deposit
    function _mint(address _to, uint256 _amount) internal override {
        harvest();
        _updateReward(_to);
        // ...
    }

    // Withdraw
    function _burn(address _from, uint256 _amount) internal override {
        harvest();
        _updateReward(_from);
        // ...
    }

    function _transfer(
        address _from,
        address _to,
        uint256 _amount
    ) internal override {
        harvest();
        _updateReward(_from);
        _updateReward(_to);
        // ...
    }
   
    // ...
    
    function harvest() public {
        // ...
        
        rewardPerShare += rewardEarned * PRECISION / totalSupply();
        
        // ...
    }

    // ...
    
    function _updateReward(address _account) internal {
        UserRewardInfo storage info = userRewardInfo[_account];
        
        info.pendingReward +=
            (balanceOf(_account) * (rewardPerShare - info.rewardPerShareOld)) /
            PRECISION;
        info.rewardPerShareOld = rewardPerShare;
    }
}
```

##### Pros
- Easy to implement
- Fair

##### Cons
- Variable and high gas cost that is protocol dependent (counterparty risk). Deposits/withdrawals/transfers would fail if the protocol's rewards logic is bricked.

#### 3. Screw it

If a user deposits at step $n$, they only earn rewards starting from step $(n+1)$ and if they withdraw just before step $n$, they give up all rewards for step $n$.

Therefore, the rewards between steps $n_1$ and $n_2$ are
$$
\Delta R_{u}(n_1, n_2) = \sum_{n_1}^{n_2} \frac{\hat s_{jn}}{\hat S_{n}}\cdot \Delta r_n
$$

where $\hat s_{jn}$ and $\hat S_n$ are the adjusted user and total shares that only take into account shares that were deposited before the current harvest epoch.

Now, assume user $u$ does $(m_u-1)$ deposit/withdraw operations at harvest epoch $n$ that spans $T_{n-1}\to{T_n}$. These occur at times $^ut_k$. Let $^ut_m = T_n$ and $^ut_0 = T_{n - 1}$ and $\Delta T_n = T_n - T_{n-1}$. Take $\Delta ^js_{k} = s_j(t_k) - s_j(t_{k-1})$ which represents the shares minted/burnt during an operation.

$\hat s_{jn}$ can be written as

$$
\hat s_{jn} = s_{jn} - \sum_{k=1}^m {^j}I_{k}\cdot \Delta^j s_{k}
$$

where

$$
^jI_k = 
\begin{cases}
1 & \text{if } \Delta^j s_{k} > 0 &\text{(deposits)}\\
0 & \text{otherwise}
\end{cases}
$$

Taking $c_{jn} = \sum_{k=1}^m {^j}I_{k}\cdot \Delta^j s_{k}$
$$
\begin{align*}
\hat s_{jn} &= s_{jn} - c_{jn}\\\\
\hat S_{n}  &= \sum_j (s_{jn} - c_{jn})\\
            &= S_n - \sum_j c_{jn}\\
            &= S_n - C_n
\end{align*}
$$

Therefore, the total rewards for user $u$ between steps $n_1$ and $n_2$ are

$$
\Delta R_{u}(n_1, n_2) = \sum_{n_1}^{n_2} \frac{s_{un}-c_{un}}{S_{n}-C_{n}}\cdot \Delta r_n
$$

Again, assume the user's shares are $s_{un}$ constant in this timeframe. This implies $c_{un}=0\ \forall\ n>n_1$. The reward earned by thte user during this period is

$$
\Delta R_{u}(n_1, n_2) = s_{un_2}\cdot \left[A(n_2) - A(n_1)\right] - c_{un_1}B(n_1)
$$

where $A(n) = \sum_{i=1}^{n} \frac{\Delta r_i}{S_i - C_i}$ and $B(n)=\frac{\Delta r_n}{S_n - C_n}$

And the total rewards earned are:
$$
R_{u}(n_2) = R_u(n_1) + s_{un_2}\cdot \left[A(n_2) - A(n_1)\right] - c_{un_1}B(n_1)
$$

```solidity
// Simplified
contract Vault is ERC20,
    // ... 
{
    // ...
    
    uint256 rewardPerShare;
    // epoch -> reward per share
    mapping(uint256 => uint256) rewardPerShareEpoch;
    
    struct UserRewardInfo {
        uint256 pendingReward;
        uint256 rewardPerShareOld;
        uint256 lastEpoch;
        uint256 sharesCorrection;
    }    
    // user -> struct
    mapping(address => UserRewardInfo) userRewardInfo;
    
    uint256 sharesCorrection;
    uint256 lastEpoch;
    
    // ...

    // Deposit
    function _mint(address _to, uint256 _amount) internal override {
        _updateReward(_to, _amount);
        // ...
    }

    // Withdraw
    function _burn(address _from, uint256 _amount) internal override {
        _updateReward(_from, 0);
        // ...
    }

    function _transfer(
        address _from,
        address _to,
        uint256 _amount
    ) internal override {
        _updateReward(_from, 0);
        _updateReward(_to, _amount);
        // ...
    }
   
    // ...
    
    function harvest() public {
        uint256 rewardEarned;
        // ...
        
        uint256 totalSharesCorrected = totalSupply() - sharesCorrection;
        uint256 rewardPerShareInc = (rewardEarned * PRECISION) /
            totalSharesCorrected;
        
        rewardPerShare += totalSharesCorrected > 0 ? rewardPerShareInc : 0;
        rewardPerShareEpoch[epochId] = rewardPerShareInc;
        sharesCorrection = 0;
        // ...
    }

    // ...
    
    function _updateReward(address _account, uint256 _correction) internal {
        UserRewardInfo storage info = userRewardInfo[_account];
        
        uint256 userLastEpoch = info.lastEpoch;

        // Has a harvest occured since user last did a deposit/withdraw/mint?
        if (userLastEpoch < lastEpoch) {
            info.pendingReward +=
                (balanceOf(_account) *
                    (rewardPerShare - info.rewardPerShareOld) -
                    info.sharesCorrection *
                    rewardPerShareEpoch[userLastEpoch]) /
                PRECISION;
            info.sharesCorrection = _correction;
            info.lastEpoch = lastEpoch;
        } else {
            info.sharesCorrection += _correction;
        }
        
        info.rewardPerShareOld = rewardPerShare;
    }
}
``` 

##### Pros
- Moderately easy to implement
- Somewhat fair

##### Cons
- Higher gas cost
- Not great when harvests are infrequent

#### 4. Do it

Consider the situation where there are two users $u_1$ and $u_2$. User $u_1$ deposited into the vault before the current harvest epoch began while $u_2$ deposited the same amount at the middle of the current harvest epoch. Ideally $u_2$ should get half the rewards as $u_1$ for the current epoch.

```
h1 <------> h2
u1 |------|
u2    |---|
```


To solve this, instead of the reward being proportional to the user's shares, it should be proportional to the shares multiplied by the time spent in vault. Let's call this quantity points and denote it as $p = \int s(t) dt$.

![](https://i.imgur.com/D70mudl.png)

The total points of the user for the harvest epoch $(n-1)\to n$ is $p_{jn}$ and total points for harvest $P_n = \sum_{j}p_{jn}$. Therefore, the rewards between steps $n_1$ and $n_2$ are

$$
\Delta R_{u}(n_1, n_2) = \sum_{n_1}^{n_2} \frac{p_{un}}{P_n}\cdot \Delta r_n
$$

Similar to the previous section, $p_{jn}$ can be written as 

$$
p_{jn} = \sum_{k=1}^{m_j} s_{j}(^jt_{k-1}) (^jt_k - ^jt_{k-1})
$$

$$
\begin{align*}
p_{jn} &= \sum_{k=1}^{m} s_{j}(t_{k-1}) (t_k - t_{k-1})\\
       &= \sum_{k=1}^{m} t_k s_{j}(t_{k-1}) - \sum_{k=1}^{m}t_{k-1} s_{j}(t_{k-1})\\
       &= \left[t_{m} s_{j}(t_{m})  +\sum_{k=1}^{m-1} t_k s_{j}(t_{k-1})\right] - \left[\sum_{k=1}^{m-1}t_{k} s_{j}(t_{k}) + t_0 s_j(t_0)\right]\\
       &= T_n s_{jn} - T_{n-1}s_j(T_{n-1}) - \sum_{k=1}^{m-1} t_k\cdot [s_{j}(t_{k}) - s_{j}(t_{k-1})]
\end{align*}
$$

Note that $\sum_{k=1}^{m-1} \Delta ^js_{k} = s_{jn} - s_j(T_{n-1})$. 
Substituting all $s_j(t_k)$ where $k < m$ in terms of $\Delta ^js_{k}$ gives

$$
\begin{align*}
p_{jn} &= T_n s_{jn} - T_{n-1}\left[s_{jn} - \sum_{k=1}^{m-1} {^j}\Delta s_{k}\right] - \sum_{k=1}^{m-1} t_k \Delta ^js_{k}\\
       &= \Delta T_n s_{jn} - \sum_{k=1}^{m-1} [t_k - T_{n-1}] \Delta ^js_{k}\\
       &= \Delta T_n s_{jn} - c_{jn}
\end{align*}
$$

where $c_{jn}$ can be thought of as a correction term accounting for deposits/withdrawals in between harvests.

Another way to derive the above expression is to take the shares-time curve and notice that points represent the area (highlighted red in the diagram below) under this curve. This area can also be represented as the total rectangular area ($\Delta T_n s_{jn}$) minus the summation of the grey areas $\left(\sum_{k=1}^{m} [t_k - T_{n-1}] \Delta s_{jk}\right)$. 

![](https://i.imgur.com/qM7E06E.png)


The total points of the vault can be calculated as

$$
\begin{align*}
P_n = \sum_{j}p_{jn} &= \sum_{j} \left[\Delta T_n s_{jn} - c_{jn}\right]\\
                     &= \Delta T_n S_n - \sum_{j} c_{jn}\\
                     &= \Delta T_n S_n - C_n
\end{align*}
$$
where again $C_n$ can be thought of as a correction accounting for deposits and withdrawals within a harvest period. We keep track of $c_{jn}$ and $C_n$ by accumulating them at each operation. 

Substituting these to the original expression gives

$$
\begin{align*}
\Delta R_{u}(n_1, n_2) &= \sum_{n_1}^{n_2} \frac{\Delta T_n s_{un} - c_{un}}{\Delta T_n S_n - C_n}\cdot \Delta r_n\\
                       &= s_u \sum_{n_1}^{n_2} \frac{\Delta T_n\cdot \Delta r_n}{\Delta T_n S_n - C_n} - \frac{c_{un_1}\cdot \Delta r_{n_1}}{\Delta T_{n_1} S_{n_1} - C_{n_1}}\\
    &= s_u \left[\sum_{i=1}^{n_2} \frac{\Delta T_n\cdot \Delta r_n}{\Delta T_n S_n - C_n} - \sum_{i=1}^{n_1} \frac{\Delta T_n\cdot \Delta r_n}{\Delta T_n S_n - C_n}\right] - \frac{c_{un_1}\cdot \Delta r_{n_1}}{\Delta T_{n_1} S_{n_1} - C_{n_1}}
\end{align*}
$$

Let $A(n) = \sum_{i=1}^{n} \frac{\Delta T_i\cdot \Delta r_i}{\Delta T_i S_i - C_i}$ and $B(n) = \frac{\Delta r_{n}}{\Delta T_{n} S_{n} - C_{n}}$

$$
R_{u}(n_2) = R_u(n_1) + s_u\left[A(n_2) - A(n_1)\right] - c_{un_1}B(n_1)
$$

##### Pros
- Fair

##### Cons
- High gas cost for deposits/withdrawals

```solidity
// Simplified
contract Vault is ERC20,
    // ... 
{
    // ...
    
    uint256 lastEpoch;
    
    uint256 rewardPerShare;
    
    struct UserRewardInfo {
        uint256 pendingReward;
        uint256 rewardPerShareOld;
        int256 pointsCorrection; // Correction factor
        uint256 lastEpoch;
    }
    // user -> struct
    mapping(address => UserRewardInfo) userRewardInfo;

    uint256 public rewardPerPoint; // Correction factor
    
    // epoch => amount
    mapping(uint256 => uint256) public pointsCorrection;

    uint256 lastHarvestTime;
    
    // ...

    // Deposit
    function _mint(address _to, uint256 _amount) internal override {
        _updateReward(_to, _sharesDiff);
        // ...
    }

    // Withdraw
    function _burn(address _from, uint256 _amount) internal override {
        _updateReward(_from, _sharesDiff);
        // ...
    }

    function _transfer(
        address _from,
        address _to,
        uint256 _amount
    ) internal override {
        _updateReward(_from, _sharesDiff);
        _updateReward(_to, _sharesDiff);
        // ...
    }
   
    // ...
    
    function harvest() public {
        uint256 rewardEarned;
        // ...
        
        uint256 timeSinceHarvest = block.timestamp - lastHarvestTime;
        
        uint256 totalPoints = timeSinceHarvest *
            totalSupply() -
            pointsCorrection;
      
        rewardPerPointEpoch[lastEpoch] =
            (rewardEarned * PRECISION) /
            totalPoints;
        rewardPerShare +=
            (timeSinceHarvest * rewardEarned * PRECISION) /
            totalPoints;
        
        lastUpdateTimestamp = block.timestamp;
        lastEpoch += 1;
        pointsCorrection = 0;
        
        // ...
    }
    
    // ...
    
    function _updateReward(address _account, int256 _sharesDiff) internal {
        UserRewardInfo storage info = userRewardInfo[_account];

        uint256 timeSinceHarvest = block.timestamp - lastHarvestTime;        
        uint256 userPointsCorrection = timeSinceHarvest * _sharesDiff;
        uint256 userLastEpoch = info.lastEpoch;
        
        pointsCorrection += userPointsCorrection;
        
        // Has a harvest occured since user last did a deposit/withdraw/mint?
        if (lastEpoch > userLastEpoch) {
            info.pendingReward +=
                ((balanceOf(_account) *
                    (rewardPerShare - info.rewardPerShareOld)) -
                    (info.pointsCorrection *
                        rewardPerPointEpoch[userLastEpoch])) /
                PRECISION;
            info.pointsCorrection = userPointsCorrection;
            info.lastEpoch = lastEpoch;
        } else {
            info.pendingReward +=
                (balanceOf(_account) *
                    (rewardPerShare - info.rewardPerShareOld)) /
                PRECISION;
            info.pointsCorrection += userPointsCorrection;
        }

        info.rewardPerShareOld = rewardPerShare;
    }
}
```