Overview

This document outlines the implementation of a custom reward distribution system to replace Merkl in our DeFi platform. The new system aims to maintain efficiency while providing more flexibility and control over reward distribution.

Key Components

  1. RewardDistributor Contract: Main contract for reward calculation and distribution
  2. EpochManager: Manages epoch-based reward accrual
  3. UserRewardState: Struct to track user rewards
  4. PoolRewardState: Struct to track pool-level rewards

Core Functionality

1. Epoch Management

struct Epoch {
    uint256 startTime;
    uint256 endTime;
    uint256 totalRewards;
}

mapping(uint256 => Epoch) public epochs;
uint256 public currentEpochId;

function flipEpoch() external {
    require(block.timestamp >= epochs[currentEpochId].endTime, "Current epoch not finished");
    currentEpochId++;
    epochs[currentEpochId] = Epoch({
        startTime: block.timestamp,
        endTime: block.timestamp + 7 days,
        totalRewards: calculateEpochRewards()
    });
}
  • Epochs last 7 days
  • flipEpoch function transitions to the next epoch

2. Reward Accrual

struct UserRewardState {
    uint256 lastUpdateEpoch;
    uint256 accruedRewards;
}

struct PoolRewardState {
    uint256 rewardPerTokenStored;
    uint256 lastUpdateTime;
}

mapping(address => mapping(address => UserRewardState)) public userRewardState;
mapping(address => PoolRewardState) public poolRewardState;

function updateReward(address user, address pool) internal {
    PoolRewardState storage poolState = poolRewardState[pool];
    UserRewardState storage userState = userRewardState[user][pool];
    
    poolState.rewardPerTokenStored = rewardPerToken(pool);
    poolState.lastUpdateTime = lastTimeRewardApplicable();
    
    if (user != address(0)) {
        userState.accruedRewards = earned(user, pool);
        userState.lastUpdateEpoch = currentEpochId;
    }
}
  • updateReward function calculates and updates rewards for a user in a specific pool

3. Reward Calculation

function rewardPerToken(address pool) public view returns (uint256) {
    PoolRewardState storage poolState = poolRewardState[pool];
    if (totalSupply(pool) == 0) {
        return poolState.rewardPerTokenStored;
    }
    return poolState.rewardPerTokenStored + (
        (lastTimeRewardApplicable() - poolState.lastUpdateTime) * rewardRate(pool) * 1e18 / totalSupply(pool)
    );
}

function earned(address user, address pool) public view returns (uint256) {
    UserRewardState storage userState = userRewardState[user][pool];
    return (balanceOf(user, pool) * (rewardPerToken(pool) - userState.rewardPerTokenStored) / 1e18) + userState.accruedRewards;
}
  • rewardPerToken calculates the reward per token for a pool
  • earned calculates the total rewards earned by a user in a pool

4. Reward Distribution

function getReward(address user, address[] memory pools) external {
    uint256 reward = 0;
    for (uint i = 0; i < pools.length; i++) {
        updateReward(user, pools[i]);
        reward += userRewardState[user][pools[i]].accruedRewards;
        userRewardState[user][pools[i]].accruedRewards = 0;
    }
    if (reward > 0) {
        RETRO.safeTransfer(user, reward);
    }
}
  • getReward function allows users to claim rewards from multiple pools

Integration with ve3,3 and Gauges

function calculatePoolReward(address pool) internal view returns (uint256) {
    return epochs[currentEpochId].totalRewards * gaugeWeights[pool] / totalGaugeWeight;
}

function applyBoost(address user, uint256 baseReward) internal view returns (uint256) {
    uint256 boost = calculateBoost(user);
    return baseReward * boost / 10000; // Assuming boost is in basis points
}

function calculateBoost(address user) internal view returns (uint256) {
    uint256 veLock = veNFT.balanceOf(user);
    // Implement boost calculation based on veLock
    // Return boost factor in basis points (e.g., 10000 = 1x, 25000 = 2.5x)
}
  • calculatePoolReward determines reward allocation for a pool based on gauge weights
  • applyBoost applies the ve3,3 boost to a user’s rewards
  • calculateBoost computes the boost factor based on a user’s ve3,3 position

Optimization Techniques

  1. Lazy Updating: Update rewards only when a user interacts with the system
  2. Batched Updates: Allow updating multiple pools in a single transaction
  3. Gas-Efficient Storage: Use packed structs and efficient data types

Implementation Steps

  1. Deploy RewardDistributor contract
  2. Integrate with existing VoterV3 and veNFT contracts
  3. Modify liquidity pool contracts to interact with RewardDistributor
  4. Update frontend to use new reward claiming and viewing functions

Testing and Verification

  1. Unit test each component (epoch management, reward calculation, distribution)
  2. Integration tests with ve3,3 and gauge systems
  3. Stress tests with multiple users and pools
  4. Gas optimization tests

Upgrade Considerations

  1. Implement the new system as upgradeable contracts
  2. Plan for a migration period from Merkl to the new system
  3. Provide backwards compatibility for a transition period if possible