// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "./IERC20.sol"; import "./ERC20Permit.sol"; import "../access/AccessControl.sol"; import "../utils/RevertMsgExtractor.sol"; import "../token/MinimalTransferHelper.sol"; import "../utils/Cast.sol"; /// @dev A token inheriting from ERC20Rewards will reward token holders with a rewards token. /// The rewarded amount will be a fixed wei per second, distributed proportionally to token holders /// by the size of their holdings. contract ERC20Rewards is AccessControl, ERC20Permit { using MinimalTransferHelper for IERC20; using Cast for uint256; event RewardsTokenSet(IERC20 token); event RewardsSet(uint32 start, uint32 end, uint256 rate); event RewardsPerTokenUpdated(uint256 accumulated); event UserRewardsUpdated(address user, uint256 userRewards, uint256 paidRewardPerToken); event Claimed(address user, address receiver, uint256 claimed); struct RewardsPeriod { uint32 start; // Start time for the current rewardsToken schedule uint32 end; // End time for the current rewardsToken schedule } struct RewardsPerToken { uint128 accumulated; // Accumulated rewards per token for the period, scaled up by 1e18 uint32 lastUpdated; // Last time the rewards per token accumulator was updated uint96 rate; // Wei rewarded per second among all token holders } struct UserRewards { uint128 accumulated; // Accumulated rewards for the user until the checkpoint uint128 checkpoint; // RewardsPerToken the last time the user rewards were updated } IERC20 public rewardsToken; // Token used as rewards RewardsPeriod public rewardsPeriod; // Period in which rewards are accumulated by users RewardsPerToken public rewardsPerToken; // Accumulator to track rewards per token mapping (address => UserRewards) public rewards; // Rewards accumulated by users constructor(string memory name, string memory symbol, uint8 decimals) ERC20Permit(name, symbol, decimals) { } /// @dev Return the earliest of two timestamps function earliest(uint32 x, uint32 y) internal pure returns (uint32 z) { z = (x < y) ? x : y; } /// @dev Set a rewards token. /// @notice Careful, this can only be done once. function setRewardsToken(IERC20 rewardsToken_) external auth { require(rewardsToken == IERC20(address(0)), "Rewards token already set"); rewardsToken = rewardsToken_; emit RewardsTokenSet(rewardsToken_); } /// @dev Set a rewards schedule function setRewards(uint32 start, uint32 end, uint96 rate) external auth { require( start <= end, "Incorrect input" ); require( rewardsToken != IERC20(address(0)), "Rewards token not set" ); // A new rewards program can be set if one is not running require( block.timestamp.u32() < rewardsPeriod.start || block.timestamp.u32() > rewardsPeriod.end, "Ongoing rewards" ); // Update the rewards per token so that we don't lose any rewards _updateRewardsPerToken(); rewardsPeriod.start = start; rewardsPeriod.end = end; // If setting up a new rewards program, the rewardsPerToken.accumulated is used and built upon // New rewards start accumulating from the new rewards program start // Any unaccounted rewards from last program can still be added to the user rewards // Any unclaimed rewards can still be claimed rewardsPerToken.lastUpdated = start; rewardsPerToken.rate = rate; emit RewardsSet(start, end, rate); } /// @dev Update the rewards per token accumulator. /// @notice Needs to be called on each liquidity event function _updateRewardsPerToken() internal { RewardsPerToken memory rewardsPerToken_ = rewardsPerToken; RewardsPeriod memory rewardsPeriod_ = rewardsPeriod; uint256 totalSupply_ = _totalSupply; // We skip the update if the program hasn't started if (block.timestamp.u32() < rewardsPeriod_.start) return; // Find out the unaccounted time uint32 end = earliest(block.timestamp.u32(), rewardsPeriod_.end); uint256 unaccountedTime = end - rewardsPerToken_.lastUpdated; // Cast to uint256 to avoid overflows later on if (unaccountedTime == 0) return; // We skip the storage changes if already updated in the same block // Calculate and update the new value of the accumulator. unaccountedTime casts it into uint256, which is desired. // If the first mint happens mid-program, we don't update the accumulator, no one gets the rewards for that period. if (totalSupply_ != 0) rewardsPerToken_.accumulated = (rewardsPerToken_.accumulated + 1e18 * unaccountedTime * rewardsPerToken_.rate / totalSupply_).u128(); // The rewards per token are scaled up for precision rewardsPerToken_.lastUpdated = end; rewardsPerToken = rewardsPerToken_; emit RewardsPerTokenUpdated(rewardsPerToken_.accumulated); } /// @dev Accumulate rewards for an user. /// @notice Needs to be called on each liquidity event, or when user balances change. function _updateUserRewards(address user) internal returns (uint128) { UserRewards memory userRewards_ = rewards[user]; RewardsPerToken memory rewardsPerToken_ = rewardsPerToken; // Calculate and update the new value user reserves. _balanceOf[user] casts it into uint256, which is desired. userRewards_.accumulated = (userRewards_.accumulated + _balanceOf[user] * (rewardsPerToken_.accumulated - userRewards_.checkpoint) / 1e18).u128(); // We must scale down the rewards by the precision factor userRewards_.checkpoint = rewardsPerToken_.accumulated; rewards[user] = userRewards_; emit UserRewardsUpdated(user, userRewards_.accumulated, userRewards_.checkpoint); return userRewards_.accumulated; } /// @dev Mint tokens, after accumulating rewards for an user and update the rewards per token accumulator. function _mint(address dst, uint256 wad) internal virtual override returns (bool) { _updateRewardsPerToken(); _updateUserRewards(dst); return super._mint(dst, wad); } /// @dev Burn tokens, after accumulating rewards for an user and update the rewards per token accumulator. function _burn(address src, uint256 wad) internal virtual override returns (bool) { _updateRewardsPerToken(); _updateUserRewards(src); return super._burn(src, wad); } /// @dev Transfer tokens, after updating rewards for source and destination. function _transfer(address src, address dst, uint wad) internal virtual override returns (bool) { _updateRewardsPerToken(); _updateUserRewards(src); _updateUserRewards(dst); return super._transfer(src, dst, wad); } /// @dev Claim all rewards from caller into a given address function claim(address to) external returns (uint256 claiming) { claiming = _claim(msg.sender, to); } /// @dev Trigger a claim for any user function remit(address user) external returns (uint256 claiming) { claiming = _claim(user, user); } /// @dev Claim all rewards from a user into an arbitrary receiver function _claim(address from, address to) internal returns (uint256 claiming) { _updateRewardsPerToken(); claiming = _updateUserRewards(from); rewards[from].accumulated = 0; // A Claimed event implies the rewards were set to zero rewardsToken.safeTransfer(to, claiming); emit Claimed(from, to, claiming); } }