GIP-CHALLENGE-001: ETHPool Reward Distribution Mechanism

Motivation

ETHPool provides a service where users can deposit ETH and they will receive rewards proportional to their deposits in the pool. Users must be able to take out their deposits along with their portion of rewards at any time. New rewards are deposited manually into the pool by the ETHPool team using a contract function, and these rewards get distributed to all users with a deposit before the rewards were deposited. The core requirements for reward distribution include:

  • Only the team can deposit rewards.
  • The team can deposit rewards at any time.
  • Deposited rewards go to the pool of users, not to individual users.
  • Users should be able to withdraw their deposits along with their share of rewards considering the time when they deposited. They should not get rewards for the ones distributed before their deposits.

Proposed solution:

My proposed solution is to use an epoch-based reward distribution mechanism. User deposits and reward deposits are stored at the epoch at which the deposits happened. An epoch can be any easily trackable, measurable, unit of count. In my implementation, I chose to use the block number of the transaction. Rewards are only distributed to user’s who have deposited their ETH into the pool before the epoch of that reward, not on, or after. A tricky part of this approach is that users may use old deposits to accrue more reward on new reward deposits. Here’s an example from the challenge:

Let say we have users A and B and team T.

User A deposits 100 at epoch 1001, and user B deposits 300 at epoch 1001 for a total of 400 in the pool. Now user A*has 25% of the pool and user B has 75%. If team T deposits 200 rewards at epoch 1002, user A should be able to withdraw 150 and user B 450, respectively.

If we have the follow scenario:
Say user a A deposits another 100 at epoch 1004, and team T deposits 300 rewards at epoch 1006. User A should be able to access the rewards at epoch 1002 and epoch 1006. But how much of the reward at epoch 1006 should user A have access to? 40% would be incorrect. Because user A is maliciously trying to use the deposit at epoch 1001 and 1004 to access more rewards, however, user A should only access rewards at each epoch based on the total amount of user deposits made at that epoch.

To correctly implement this solution, every user deposit is stored as a struct in an array as follows:

struct Deposit {
  // The amount deposited
  uint256 amount;
  // The block number the deposit was made
  uint256 EpochNumber;
}

Every reward deposit is also stored as a struct in an array as follows:

struct RewardDeposit {
  // The amount deposited
  uint256 amount;
  // The block number the deposit was made
  uint256 EpochNumber;
  // The totalAmount of user deposit at deposit time
  uint256 totalUserDeposit;
}

To calculate the reward accrued in all reward deposit by a user, I used the following formula:
For all user deposits from 0…n, we check and add up all user deposits across all reward deposits using the formula:
User reward = (userDepositAtEpoch / totalUserDepositsAtEpoch) * rewardDepositAtEpoch

This approach produced the correct results for multiple user deposits and reward deposits as may be observed from the implementation tests.

The major drawback of this approach is the runtime (O(n^2)) and the use of storage which can be a little on the high side if not well optimised.

Implementation

The implementation for this can logic found here.