Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

👷 Add dual reward distribution #253

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading