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

Dual rewards audit fixes #257

Merged
merged 6 commits into from
Aug 20, 2024
Merged
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
30 changes: 22 additions & 8 deletions contracts/StakingRewardsV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ contract StakingRewardsV2 is
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
IERC20 public immutable usdc;

/// @notice Used to scale USDC precision to 18 decimals
uint256 private constant PRECISION = 1e12;

/*///////////////////////////////////////////////////////////////
STATE
///////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -156,7 +159,10 @@ contract StakingRewardsV2 is
/// @param _rewardEscrow The address for the RewardEscrowV2 contract
/// @param _rewardsNotifier The address for the StakingRewardsNotifier contract
constructor(address _kwenta, address _usdc, address _rewardEscrow, address _rewardsNotifier) {
if (_kwenta == address(0) || _usdc == address(0) || _rewardEscrow == address(0) || _rewardsNotifier == address(0)) {
if (
_kwenta == address(0) || _usdc == address(0) || _rewardEscrow == address(0)
|| _rewardsNotifier == address(0)
) {
revert ZeroAddress();
}

Expand Down Expand Up @@ -369,7 +375,7 @@ contract StakingRewardsV2 is
rewardEscrow.appendVestingEntry(_to, reward);
}

uint256 rewardUSDC = rewardsUSDC[_account];
uint256 rewardUSDC = rewardsUSDC[_account] / PRECISION;
if (rewardUSDC > 0) {
// update state (first)
rewardsUSDC[_account] = 0;
Expand Down Expand Up @@ -431,6 +437,10 @@ contract StakingRewardsV2 is
return rewardRate * rewardsDuration;
}

function getRewardForDurationUSDC() external view returns (uint256) {
return rewardRateUSDC * rewardsDuration;
}

/// @inheritdoc IStakingRewardsV2
function rewardPerToken() public view returns (uint256) {
uint256 allTokensStaked = totalSupply();
Expand All @@ -452,7 +462,10 @@ contract StakingRewardsV2 is
}

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

/// @inheritdoc IStakingRewardsV2
Expand All @@ -472,8 +485,9 @@ contract StakingRewardsV2 is
function earnedUSDC(address _account) public view returns (uint256) {
uint256 totalBalance = balanceOf(_account);

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

/*///////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -560,7 +574,7 @@ contract StakingRewardsV2 is
/// @param _timestamp: timestamp to check
/// @dev returns 0 if no checkpoints exist, uses iterative binary search
/// @dev if called with a timestamp that equals the current block timestamp, then the function might return inconsistent
/// values as further transactions changing the balances can still occur within the same block.
/// values as further transactions changing the balances can still occur within the same block.
function _checkpointBinarySearch(Checkpoint[] storage _checkpoints, uint256 _timestamp)
internal
view
Expand Down Expand Up @@ -649,15 +663,15 @@ contract StakingRewardsV2 is
{
if (block.timestamp >= periodFinish) {
rewardRate = _reward / rewardsDuration;
rewardRateUSDC = _rewardUsdc / rewardsDuration;
rewardRateUSDC = (_rewardUsdc * PRECISION) / rewardsDuration;
} else {
uint256 remaining = periodFinish - block.timestamp;

uint256 leftover = remaining * rewardRate;
rewardRate = (_reward + leftover) / rewardsDuration;

uint256 leftoverUsdc = remaining * rewardRateUSDC;
rewardRateUSDC = (_rewardUsdc + leftoverUsdc) / rewardsDuration;
rewardRateUSDC = (_rewardUsdc * PRECISION + leftoverUsdc) / rewardsDuration;
}

lastUpdateTime = block.timestamp;
Expand Down
16 changes: 6 additions & 10 deletions test/foundry/unit/StakingRewardsV2/StakingRewardsV2.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -388,13 +388,7 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup {

function test_Can_Recover_Non_Staking_Token() public {
// create mockToken
IERC20 mockToken = new Kwenta(
"Mock",
"MOCK",
INITIAL_SUPPLY,
address(this),
treasury
);
IERC20 mockToken = new Kwenta("Mock", "MOCK", INITIAL_SUPPLY, address(this), treasury);

// transfer in non staking tokens
vm.prank(treasury);
Expand Down Expand Up @@ -474,7 +468,7 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup {
vm.warp(block.timestamp + 1 weeks);

// check reward per token updated
assertEq(stakingRewardsV2.rewardPerTokenUSDC(), 1 ether);
assertEq(stakingRewardsV2.rewardPerTokenUSDC(), 1 ether * PRECISION);
}

/*//////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -722,7 +716,7 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup {

// configure reward rate
vm.prank(address(rewardsNotifier));
stakingRewardsV2.notifyRewardAmount(0 , TEST_VALUE);
stakingRewardsV2.notifyRewardAmount(0, TEST_VALUE);

// fast forward 2 weeks
vm.warp(block.timestamp + 2 weeks);
Expand Down Expand Up @@ -761,7 +755,9 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup {
assertEq(finalRewardRate / 2, initialRewardRate);
}

function test_rewardRateUSDC_Should_Increase_If_New_Rewards_Come_Before_Duration_Ends() public {
function test_rewardRateUSDC_Should_Increase_If_New_Rewards_Come_Before_Duration_Ends()
public
{
fundAndApproveAccountV2(address(this), 1 weeks);

uint256 totalToDistribute = 5 ether;
Expand Down
Loading
Loading