Skip to content

Commit

Permalink
👷 Add dual reward distribution
Browse files Browse the repository at this point in the history
  • Loading branch information
Flocqst committed Jul 18, 2024
1 parent 9dee0eb commit 6194e5c
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 2 deletions.
79 changes: 78 additions & 1 deletion contracts/StakingRewardsV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,24 @@ contract StakingRewardsV2 is
/// @notice tracks all addresses approved to take actions on behalf of a given account
mapping(address => mapping(address => bool)) public operatorApprovals;

/// @notice Contract for USDC ERC20 token - used for rewards
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
IERC20 public immutable usdc;

/// @notice amount of tokens minted per second
uint256 public rewardRateUSDC;

/// @notice summation of rewardRate divided by total staked tokens
uint256 public rewardPerTokenStoredUSDC;

/// @notice represents the rewardPerToken for USDC rewards
/// value the last time the staker calculated earned() rewards
mapping(address => uint256) public userRewardPerTokenPaidUSDC;

/// @notice track USDC rewards for a given user which changes when
/// a user stakes, unstakes, or claims rewards
mapping(address => uint256) public rewardsUSDC;

/*///////////////////////////////////////////////////////////////
AUTH
///////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -134,9 +152,10 @@ contract StakingRewardsV2 is
/// Actual contract construction will take place in the initialize function via proxy
/// @custom:oz-upgrades-unsafe-allow constructor
/// @param _kwenta The address for the KWENTA ERC20 token
/// @param _usdc The address for the USDC ERC20 token
/// @param _rewardEscrow The address for the RewardEscrowV2 contract
/// @param _rewardsNotifier The address for the StakingRewardsNotifier contract
constructor(address _kwenta, address _rewardEscrow, address _rewardsNotifier) {
constructor(address _kwenta, address _usdc, address _rewardEscrow, address _rewardsNotifier) {
if (_kwenta == address(0) || _rewardEscrow == address(0) || _rewardsNotifier == address(0))
{
revert ZeroAddress();
Expand All @@ -146,6 +165,7 @@ contract StakingRewardsV2 is

// define reward/staking token
kwenta = IKwenta(_kwenta);
usdc = IERC20(_usdc);

// define contracts which will interact with StakingRewards
rewardEscrow = IRewardEscrowV2(_rewardEscrow);
Expand Down Expand Up @@ -356,6 +376,19 @@ contract StakingRewardsV2 is
// as newly issued rewards from inflation are now issued as non-escrowed
kwenta.transfer(_to, reward);
}

uint256 rewardUSDC = rewardsUSDC[_account];
if (rewardUSDC > 0) {
// update state (first)
rewardsUSDC[_account] = 0;

// emit reward claimed event and index account
emit RewardPaidUSDC(_account, rewardUSDC);

// transfer token from this contract to the account
// as newly issued rewards from inflation are now issued as non-escrowed
usdc.transfer(_to, rewardUSDC);
}
}

function _getRewardCompounding(address _account)
Expand Down Expand Up @@ -400,6 +433,7 @@ contract StakingRewardsV2 is

function _updateReward(address _account) internal {
rewardPerTokenStored = rewardPerToken();
rewardPerTokenStoredUSDC = rewardPerTokenUSDC();
lastUpdateTime = lastTimeRewardApplicable();

if (_account != address(0)) {
Expand All @@ -409,6 +443,10 @@ contract StakingRewardsV2 is
// update reward per token staked AT this given time
// (i.e. when this user is interacting with StakingRewards)
userRewardPerTokenPaid[_account] = rewardPerTokenStored;

rewardsUSDC[_account] = earnedUSDC(_account);

userRewardPerTokenPaidUSDC[_account] = rewardPerTokenStoredUSDC;
}
}

Expand All @@ -429,6 +467,18 @@ contract StakingRewardsV2 is
+ (((lastTimeRewardApplicable() - lastUpdateTime) * rewardRate * 1e18) / allTokensStaked);
}

/// @inheritdoc IStakingRewardsV2
function rewardPerTokenUSDC() public view returns (uint256) {
uint256 allTokensStaked = totalSupply();

if (allTokensStaked == 0) {
return rewardPerTokenStoredUSDC;
}

return rewardPerTokenStoredUSDC
+ (((lastTimeRewardApplicable() - lastUpdateTime) * rewardRateUSDC * 1e18) / allTokensStaked);
}

/// @inheritdoc IStakingRewardsV2
function lastTimeRewardApplicable() public view returns (uint256) {
return block.timestamp < periodFinish ? block.timestamp : periodFinish;
Expand All @@ -442,6 +492,14 @@ contract StakingRewardsV2 is
+ rewards[_account];
}

/// @inheritdoc IStakingRewardsV2
function earnedUSDC(address _account) public view returns (uint256) {
uint256 totalBalance = balanceOf(_account);

return ((totalBalance * (rewardPerTokenUSDC() - userRewardPerTokenPaidUSDC[_account])) / 1e18)
+ rewardsUSDC[_account];
}

/*///////////////////////////////////////////////////////////////
DELEGATION
///////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -626,6 +684,25 @@ contract StakingRewardsV2 is
emit RewardAdded(_reward);
}

/// @inheritdoc IStakingRewardsV2
function notifyUsdcRewardAmount(uint256 _reward)
external
onlyRewardsNotifier
updateReward(address(0))
{
if (block.timestamp >= periodFinish) {
rewardRate = _reward / rewardsDuration;
} else {
uint256 remaining = periodFinish - block.timestamp;
uint256 leftover = remaining * rewardRate;
rewardRate = (_reward + leftover) / rewardsDuration;
}

lastUpdateTime = block.timestamp;
periodFinish = block.timestamp + rewardsDuration;
emit UsdcRewardAdded(_reward);
}

/// @inheritdoc IStakingRewardsV2
function setRewardsDuration(uint256 _rewardsDuration) external onlyOwner {
if (block.timestamp <= periodFinish) revert RewardsPeriodNotComplete();
Expand Down
25 changes: 24 additions & 1 deletion contracts/interfaces/IStakingRewardsV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,19 @@ interface IStakingRewardsV2 {
/// @return running sum of reward per total tokens staked
function rewardPerToken() external view returns (uint256);

/// @notice calculate running sum of USDC reward per total tokens staked
/// at this specific time
/// @return running sum of USDC reward per total tokens staked
function rewardPerTokenUSDC() external view returns (uint256);

/// @notice get the last time a reward is applicable for a given user
/// @return timestamp of the last time rewards are applicable
function lastTimeRewardApplicable() external view returns (uint256);

/// @notice determine how much USDC reward an account has earned thus far
/// @param _account: address of account earned amount is being calculated for
function earnedUSDC(address _account) external view returns (uint256);

/// @notice determine how much reward token an account has earned thus far
/// @param _account: address of account earned amount is being calculated for
function earned(address _account) external view returns (uint256);
Expand Down Expand Up @@ -197,6 +206,11 @@ interface IStakingRewardsV2 {
/// @dev updateReward() called prior to function logic (with zero address)
function notifyRewardAmount(uint256 _reward) external;

/// @notice configure usdc reward rate
/// @param _reward: amount of usdc to be distributed over a period
/// @dev updateReward() called prior to function logic (with zero address)
function notifyUsdcRewardAmount(uint256 _reward) external;

/// @notice set rewards duration
/// @param _rewardsDuration: denoted in seconds
function setRewardsDuration(uint256 _rewardsDuration) external;
Expand Down Expand Up @@ -229,6 +243,10 @@ interface IStakingRewardsV2 {
/// @param reward: amount to be distributed over applicable rewards duration
event RewardAdded(uint256 reward);

/// @notice update reward rate
/// @param reward: amount to be distributed over applicable rewards duration
event UsdcRewardAdded(uint256 reward);

/// @notice emitted when user stakes tokens
/// @param user: staker address
/// @param amount: amount staked
Expand All @@ -249,11 +267,16 @@ interface IStakingRewardsV2 {
/// @param amount: amount unstaked
event EscrowUnstaked(address user, uint256 amount);

/// @notice emitted when user claims rewards
/// @notice emitted when user claims KWENTA rewards
/// @param user: address of user claiming rewards
/// @param reward: amount of reward token claimed
event RewardPaid(address indexed user, uint256 reward);

/// @notice emitted when user claims USDC rewards
/// @param user: address of user claiming rewards
/// @param reward: amount of USDC token claimed
event RewardPaidUSDC(address indexed user, uint256 reward);

/// @notice emitted when rewards duration changes
/// @param newDuration: denoted in seconds
event RewardsDurationUpdated(uint256 newDuration);
Expand Down

0 comments on commit 6194e5c

Please sign in to comment.