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
- RewardDistributor Contract: Main contract for reward calculation and distribution
- EpochManager: Manages epoch-based reward accrual
- UserRewardState: Struct to track user rewards
- 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
- Lazy Updating: Update rewards only when a user interacts with the system
- Batched Updates: Allow updating multiple pools in a single transaction
- Gas-Efficient Storage: Use packed structs and efficient data types
Implementation Steps
- Deploy
RewardDistributor
contract
- Integrate with existing
VoterV3
and veNFT
contracts
- Modify liquidity pool contracts to interact with
RewardDistributor
- Update frontend to use new reward claiming and viewing functions
Testing and Verification
- Unit test each component (epoch management, reward calculation, distribution)
- Integration tests with ve3,3 and gauge systems
- Stress tests with multiple users and pools
- Gas optimization tests
Upgrade Considerations
- Implement the new system as upgradeable contracts
- Plan for a migration period from Merkl to the new system
- Provide backwards compatibility for a transition period if possible