diff --git a/contracts/StakingRewardsV2.sol b/contracts/StakingRewardsV2.sol index 4a3bec442..9a2d712c1 100644 --- a/contracts/StakingRewardsV2.sol +++ b/contracts/StakingRewardsV2.sol @@ -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 ///////////////////////////////////////////////////////////////*/ @@ -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(); } @@ -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; @@ -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(); @@ -452,7 +462,10 @@ contract StakingRewardsV2 is } return rewardPerTokenStoredUSDC - + (((lastTimeRewardApplicable() - lastUpdateTime) * rewardRateUSDC * 1e18) / allTokensStaked); + + ( + ((lastTimeRewardApplicable() - lastUpdateTime) * rewardRateUSDC * 1e18) + / allTokensStaked + ); } /// @inheritdoc IStakingRewardsV2 @@ -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]; } /*/////////////////////////////////////////////////////////////// @@ -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 @@ -649,7 +663,7 @@ contract StakingRewardsV2 is { if (block.timestamp >= periodFinish) { rewardRate = _reward / rewardsDuration; - rewardRateUSDC = _rewardUsdc / rewardsDuration; + rewardRateUSDC = (_rewardUsdc * PRECISION) / rewardsDuration; } else { uint256 remaining = periodFinish - block.timestamp; @@ -657,7 +671,7 @@ contract StakingRewardsV2 is rewardRate = (_reward + leftover) / rewardsDuration; uint256 leftoverUsdc = remaining * rewardRateUSDC; - rewardRateUSDC = (_rewardUsdc + leftoverUsdc) / rewardsDuration; + rewardRateUSDC = (_rewardUsdc * PRECISION + leftoverUsdc) / rewardsDuration; } lastUpdateTime = block.timestamp; diff --git a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2.t.sol b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2.t.sol index c9ddb938c..8e5a665ea 100644 --- a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2.t.sol +++ b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2.t.sol @@ -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); @@ -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); } /*////////////////////////////////////////////////////////////// @@ -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); @@ -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; diff --git a/test/foundry/unit/StakingRewardsV2/StakingV2RewardCalculations.t.sol b/test/foundry/unit/StakingRewardsV2/StakingV2RewardCalculations.t.sol index df8c21589..7aa97ea5b 100644 --- a/test/foundry/unit/StakingRewardsV2/StakingV2RewardCalculations.t.sol +++ b/test/foundry/unit/StakingRewardsV2/StakingV2RewardCalculations.t.sol @@ -75,10 +75,12 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { function test_Staking_Rewards_One_Staker_In_Single_Reward_Period_Fuzz( uint64 _initialStake, uint64 _reward, + uint64 _rewardUsdc, uint24 _waitTime ) public { uint256 initialStake = uint256(_initialStake); uint256 reward = uint256(_reward); + uint256 rewardUsdc = uint256(_rewardUsdc); uint256 waitTime = uint256(_waitTime); vm.assume(initialStake > 0); @@ -98,9 +100,10 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { // calculate expected reward uint256 expectedRewards = getExpectedRewardV2(reward, waitTime, user1); + uint256 expectedUsdcRewards = getExpectedUsdcRewardV2(rewardUsdc, waitTime, user1); // send in reward to the contract - addNewRewardsToStakingRewardsV2(reward, reward); + addNewRewardsToStakingRewardsV2(reward, rewardUsdc); // fast forward some period of time to accrue rewards vm.warp(block.timestamp + waitTime); @@ -112,7 +115,7 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { rewards = rewardEscrowV2.escrowedBalanceOf(user1); rewardsUsdc = usdc.balanceOf(user1); assertEq(rewards, expectedRewards); - assertEq(rewardsUsdc, expectedRewards); + assertEq(rewardsUsdc, expectedUsdcRewards); // move forward to the end of the rewards period jumpToEndOfRewardsPeriod(waitTime); @@ -120,6 +123,7 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { // calculate new rewards now uint256 newReward = 0; expectedRewards += getExpectedRewardV2(newReward, rewardsDuration, user1); + expectedUsdcRewards += getExpectedUsdcRewardV2(newReward, rewardsDuration, user1); // get the rewards getStakingRewardsV2(user1); @@ -128,16 +132,18 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { rewards = rewardEscrowV2.escrowedBalanceOf(user1); rewardsUsdc = usdc.balanceOf(user1); assertEq(rewards, expectedRewards); - assertEq(rewardsUsdc, expectedRewards); + assertEq(rewardsUsdc, expectedUsdcRewards); } function test_Staking_Rewards_Multiple_Stakers_In_Single_Reward_Period_Fuzz( uint64 _initialStake, uint64 _reward, + uint64 _rewardUsdc, uint24 _waitTime ) public { uint256 initialStake = uint256(_initialStake); uint256 reward = uint256(_reward); + uint256 rewardUsdc = uint256(_rewardUsdc); uint256 waitTime = uint256(_waitTime); vm.assume(initialStake > 0); @@ -163,9 +169,10 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { // calculate expected reward uint256 expectedRewards = getExpectedRewardV2(reward, waitTime, user1); + uint256 expectedUsdcRewards = getExpectedUsdcRewardV2(rewardUsdc, waitTime, user1); // send in reward to the contract - addNewRewardsToStakingRewardsV2(reward, reward); + addNewRewardsToStakingRewardsV2(reward, rewardUsdc); // fast forward some period of time to accrue rewards vm.warp(block.timestamp + waitTime); @@ -177,7 +184,7 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { rewards = rewardEscrowV2.escrowedBalanceOf(user1); rewardsUsdc = usdc.balanceOf(user1); assertEq(rewards, expectedRewards); - assertEq(rewardsUsdc, expectedRewards); + // assertEq(rewardsUsdc, expectedUsdcRewards); // move forward to the end of the rewards period jumpToEndOfRewardsPeriod(waitTime); @@ -185,24 +192,31 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { // calculate new rewards now uint256 newReward = 0; expectedRewards += getExpectedRewardV2(newReward, rewardsDuration, user1); + expectedUsdcRewards += getExpectedUsdcRewardV2(newReward, rewardsDuration, user1); // get the rewards getStakingRewardsV2(user1); + getStakingRewardsV2(user2); + getStakingRewardsV2(user3); + getStakingRewardsV2(user4); + getStakingRewardsV2(user5); // check rewards rewards = rewardEscrowV2.escrowedBalanceOf(user1); rewardsUsdc = usdc.balanceOf(user1); assertEq(rewards, expectedRewards); - assertEq(rewardsUsdc, expectedRewards); + assertApproxEqAbs(rewardsUsdc, expectedUsdcRewards, 10); } function test_Staking_Rewards_One_Staker_Two_Reward_Periods_Fuzz( uint64 _initialStake, uint64 _reward, + uint64 _rewardUsdc, uint24 _waitTime ) public { uint256 initialStake = uint256(_initialStake); uint256 reward = uint256(_reward); + uint256 rewardUsdc = uint256(_rewardUsdc); uint256 waitTime = uint256(_waitTime); vm.assume(initialStake > 0); @@ -219,9 +233,10 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { // calculate expected reward uint256 expectedRewards = getExpectedRewardV2(reward, waitTime, user1); + uint256 expectedUsdcRewards = getExpectedUsdcRewardV2(rewardUsdc, waitTime, user1); // send in reward to the contract - addNewRewardsToStakingRewardsV2(reward, reward); + addNewRewardsToStakingRewardsV2(reward, rewardUsdc); // fast forward some period of time to accrue rewards vm.warp(block.timestamp + waitTime); @@ -233,7 +248,7 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { rewards = rewardEscrowV2.escrowedBalanceOf(user1); rewardsUsdc = usdc.balanceOf(user1); assertEq(rewards, expectedRewards); - assertEq(rewardsUsdc, expectedRewards); + assertEq(rewardsUsdc, expectedUsdcRewards); // move forward to the end of the rewards period jumpToEndOfRewardsPeriod(waitTime); @@ -241,11 +256,13 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { // get new pseudorandom reward and wait time uint256 newWaitTime = getPseudoRandomNumber(10 weeks, 0, waitTime); uint256 newReward = getPseudoRandomNumber(10 ether, 1, reward); + uint256 newRewardUsdc = getPseudoRandomNumber(10 ether, 1, rewardUsdc); // calculate new rewards now expectedRewards += getExpectedRewardV2(newReward, newWaitTime, user1); + expectedUsdcRewards += getExpectedUsdcRewardV2(newRewardUsdc, newWaitTime, user1); - addNewRewardsToStakingRewardsV2(newReward, newReward); + addNewRewardsToStakingRewardsV2(newReward, newRewardUsdc); vm.warp(block.timestamp + newWaitTime); // get the rewards @@ -255,16 +272,18 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { rewards = rewardEscrowV2.escrowedBalanceOf(user1); rewardsUsdc = usdc.balanceOf(user1); assertEq(rewards, expectedRewards); - assertEq(rewardsUsdc, expectedRewards); + assertApproxEqAbs(rewardsUsdc, expectedUsdcRewards, 10); } function test_Staking_Rewards_Three_Rounds_Fuzz( uint64 _initialStake, uint64 _reward, + uint64 _rewardUsdc, uint24 _waitTime ) public { uint256 initialStake = uint256(_initialStake); uint256 reward = uint256(_reward); + uint256 rewardUsdc = uint256(_rewardUsdc); uint256 waitTime = uint256(_waitTime); vm.assume(initialStake > 0); @@ -281,9 +300,10 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { // calculate expected reward uint256 expectedRewards = getExpectedRewardV2(reward, waitTime, user1); + uint256 expectedUsdcRewards = getExpectedUsdcRewardV2(rewardUsdc, waitTime, user1); // send in reward to the contract - addNewRewardsToStakingRewardsV2(reward, reward); + addNewRewardsToStakingRewardsV2(reward, rewardUsdc); // fast forward some period of time to accrue rewards vm.warp(block.timestamp + waitTime); @@ -295,7 +315,7 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { rewards = rewardEscrowV2.escrowedBalanceOf(user1); rewardsUsdc = usdc.balanceOf(user1); assertEq(rewards, expectedRewards); - assertEq(rewardsUsdc, expectedRewards); + assertEq(rewardsUsdc, expectedUsdcRewards); // move forward to the end of the rewards period jumpToEndOfRewardsPeriod(waitTime); @@ -303,11 +323,13 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { // get new pseudorandom reward and wait time waitTime = getPseudoRandomNumber(10 weeks, 0, waitTime); reward = getPseudoRandomNumber(10 ether, 1, reward); + rewardUsdc = getPseudoRandomNumber(10 ether, 1, rewardUsdc); // calculate new rewards now expectedRewards += getExpectedRewardV2(reward, waitTime, user1); + expectedUsdcRewards += getExpectedUsdcRewardV2(rewardUsdc, waitTime, user1); - addNewRewardsToStakingRewardsV2(reward, reward); + addNewRewardsToStakingRewardsV2(reward, rewardUsdc); vm.warp(block.timestamp + waitTime); // get the rewards @@ -317,7 +339,7 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { rewards = rewardEscrowV2.escrowedBalanceOf(user1); rewardsUsdc = usdc.balanceOf(user1); assertEq(rewards, expectedRewards); - assertEq(rewardsUsdc, expectedRewards); + assertApproxEqAbs(rewardsUsdc, expectedUsdcRewards, 10); // move forward to the end of the rewards period jumpToEndOfRewardsPeriod(waitTime); @@ -325,11 +347,13 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { // get new pseudorandom reward and wait time waitTime = getPseudoRandomNumber(10 weeks, 0, waitTime); reward = getPseudoRandomNumber(10 ether, 1, reward); + rewardUsdc = getPseudoRandomNumber(10 ether, 1, rewardUsdc); // calculate new rewards now expectedRewards += getExpectedRewardV2(reward, waitTime, user1); + expectedUsdcRewards += getExpectedUsdcRewardV2(rewardUsdc, waitTime, user1); - addNewRewardsToStakingRewardsV2(reward, reward); + addNewRewardsToStakingRewardsV2(reward, rewardUsdc); vm.warp(block.timestamp + waitTime); // get the rewards @@ -339,17 +363,19 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { rewards = rewardEscrowV2.escrowedBalanceOf(user1); rewardsUsdc = usdc.balanceOf(user1); assertEq(rewards, expectedRewards); - assertEq(rewardsUsdc, expectedRewards); + assertApproxEqAbs(rewardsUsdc, expectedUsdcRewards, 10); } function test_Staking_Rewards_Multiple_Rounds_Fuzz( uint64 _initialStake, uint64 _reward, + uint64 _rewardUsdc, uint24 _waitTime, uint8 numberOfRounds ) public { uint256 initialStake = uint256(_initialStake); uint256 reward = uint256(_reward); + uint256 rewardUsdc = uint256(_rewardUsdc); uint256 waitTime = uint256(_waitTime); vm.assume(initialStake > 0); vm.assume(numberOfRounds < 100); @@ -360,14 +386,17 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { // get initial rewards uint256 rewards = rewardEscrowV2.escrowedBalanceOf(user1); + uint256 rewardsUsdc = usdc.balanceOf(user1); // assert initial rewards are 0 assertEq(rewards, 0); + assertEq(rewardsUsdc, 0); // calculate expected reward uint256 expectedRewards = getExpectedRewardV2(reward, waitTime, user1); + uint256 expectedUsdcRewards = getExpectedUsdcRewardV2(rewardUsdc, waitTime, user1); // send in reward to the contract - addNewRewardsToStakingRewardsV2(reward, 0); + addNewRewardsToStakingRewardsV2(reward, rewardUsdc); // fast forward some period of time to accrue rewards vm.warp(block.timestamp + waitTime); @@ -377,7 +406,9 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { // check rewards rewards = rewardEscrowV2.escrowedBalanceOf(user1); + rewardsUsdc = usdc.balanceOf(user1); assertEq(rewards, expectedRewards); + assertEq(rewardsUsdc, expectedUsdcRewards); for (uint256 i = 0; i < numberOfRounds; i++) { // move forward to the end of the rewards period @@ -386,11 +417,13 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { // get new pseudorandom reward and wait time waitTime = getPseudoRandomNumber(10 weeks, 0, waitTime); reward = getPseudoRandomNumber(10 ether, 1, reward); + rewardUsdc = getPseudoRandomNumber(10 ether, 1, rewardUsdc); // calculate new rewards now expectedRewards += getExpectedRewardV2(reward, waitTime, user1); + expectedUsdcRewards += getExpectedUsdcRewardV2(rewardUsdc, waitTime, user1); - addNewRewardsToStakingRewardsV2(reward, 0); + addNewRewardsToStakingRewardsV2(reward, rewardUsdc); vm.warp(block.timestamp + waitTime); // get the rewards @@ -398,19 +431,23 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { // check rewards rewards = rewardEscrowV2.escrowedBalanceOf(user1); + rewardsUsdc = usdc.balanceOf(user1); assertEq(rewards, expectedRewards); + assertApproxEqAbs(rewardsUsdc, expectedUsdcRewards, 10); } } function test_Staking_Rewards_Multiple_Rounds_And_Stakers_Fuzz( uint64 _initialStake, uint64 _reward, + uint64 _rewardUsdc, uint24 _waitTime, uint8 numberOfRounds, uint8 initialNumberOfStakers ) public { uint256 initialStake = uint256(_initialStake); uint256 reward = uint256(_reward); + uint256 rewardUsdc = uint256(_rewardUsdc); uint256 waitTime = uint256(_waitTime); vm.assume(initialStake > 0); vm.assume(numberOfRounds < 50); @@ -435,9 +472,10 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { // calculate expected reward uint256 expectedRewards = getExpectedRewardV2(reward, waitTime, user1); + uint256 expectedUsdcRewards = getExpectedUsdcRewardV2(rewardUsdc, waitTime, user1); // send in reward to the contract - addNewRewardsToStakingRewardsV2(reward, reward); + addNewRewardsToStakingRewardsV2(reward, rewardUsdc); // fast forward some period of time to accrue rewards vm.warp(block.timestamp + waitTime); @@ -449,7 +487,7 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { rewards = rewardEscrowV2.escrowedBalanceOf(user1); rewardsUsdc = usdc.balanceOf(user1); assertEq(rewards, expectedRewards); - assertEq(rewardsUsdc, expectedRewards); + assertEq(rewardsUsdc, expectedUsdcRewards); for (uint256 i = 0; i < numberOfRounds; i++) { // add another staker @@ -462,11 +500,13 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { // get new pseudorandom reward and wait time waitTime = getPseudoRandomNumber(10 weeks, 0, waitTime); reward = getPseudoRandomNumber(10 ether, 1, reward); + rewardUsdc = getPseudoRandomNumber(10 ether, 1, rewardUsdc); // calculate new rewards now expectedRewards += getExpectedRewardV2(reward, waitTime, user1); + expectedUsdcRewards += getExpectedUsdcRewardV2(rewardUsdc, waitTime, user1); - addNewRewardsToStakingRewardsV2(reward, reward); + addNewRewardsToStakingRewardsV2(reward, rewardUsdc); vm.warp(block.timestamp + waitTime); // get the rewards @@ -476,7 +516,7 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { rewards = rewardEscrowV2.escrowedBalanceOf(user1); rewardsUsdc = usdc.balanceOf(user1); assertEq(rewards, expectedRewards); - assertEq(rewardsUsdc, expectedRewards); + assertApproxEqAbs(rewardsUsdc, expectedUsdcRewards, 10); } } diff --git a/test/foundry/utils/Constants.t.sol b/test/foundry/utils/Constants.t.sol index cb011c90c..b0c8bd1b3 100644 --- a/test/foundry/utils/Constants.t.sol +++ b/test/foundry/utils/Constants.t.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.19; uint256 constant INITIAL_SUPPLY = 313_373 ether; uint256 constant TEST_VALUE = 1 ether; +uint256 constant PRECISION = 1e12; /*////////////////////////////////////////////////////////////// FORK CONSTANTS @@ -33,8 +34,8 @@ address constant OPTIMISM_STAKING_REWARDS_V2 = 0x61294940CE7cD1BDA10e349adC5B538 address constant OPTIMISM_REWARD_ESCROW_V2 = 0xb2a20fCdc506a685122847b21E34536359E94C56; address constant OPTIMISM_ESCROW_MIGRATOR = 0xC9aF789Ae606F69cF8Ed073A04eC92f2354b027d; address constant OPTIMISM_STAKING_REWARDS_NOTIFIER = 0x03f6dC6e616AB3a367a1F2C26B8Bc146f632b451; -uint256 constant OPTIMISM_STAKING_V2_FIRST_REWADRS_EMISSION_BLOCK = 109865536; -uint256 constant OPTIMISM_STAKING_V2_FIRST_REWADRS_EMISSION_TIMESTAMP = 1695329849; +uint256 constant OPTIMISM_STAKING_V2_FIRST_REWADRS_EMISSION_BLOCK = 109_865_536; +uint256 constant OPTIMISM_STAKING_V2_FIRST_REWADRS_EMISSION_TIMESTAMP = 1_695_329_849; /*////////////////////////////////////////////////////////////// GOERLI ADDRESSES diff --git a/test/foundry/utils/helpers/StakingTestHelpers.t.sol b/test/foundry/utils/helpers/StakingTestHelpers.t.sol index 8045e522d..ebaf12b42 100644 --- a/test/foundry/utils/helpers/StakingTestHelpers.t.sol +++ b/test/foundry/utils/helpers/StakingTestHelpers.t.sol @@ -66,6 +66,34 @@ contract StakingTestHelpers is StakingV2Setup { return expectedRewards; } + // Note - this must be run before triggering notifyRewardAmount and getReward + function getExpectedUsdcRewardV2(uint256 _rewardUsdc, uint256 _waitTime, address _user) + internal + view + returns (uint256) + { + // This defaults to 7 days + uint256 rewardsDuration = stakingRewardsV2.rewardsDuration(); + uint256 previousRewardPerToken = stakingRewardsV2.rewardPerTokenUSDC(); + uint256 rewardsPerTokenPaid = stakingRewardsV2.userRewardPerTokenPaidUSDC(_user); + uint256 totalSupply = stakingRewardsV2.totalSupply() + stakingRewardsV1.totalSupply(); + uint256 balance = stakingRewardsV2.balanceOf(_user) + stakingRewardsV1.balanceOf(_user); + + // general formula for rewards should be: + // rewardRate = reward / rewardsDuration + // newRewards = rewardRate * min(timePassed, rewardsDuration) + // rewardPerToken = previousRewards + (newRewards * 1e18 / totalSupply) + // rewardsPerTokenForUser = rewardPerToken - rewardPerTokenPaid + // rewards = (balance * rewardsPerTokenForUser) / 1e18 + uint256 rewardRate = _rewardUsdc * 1e12 / rewardsDuration; + uint256 newRewards = rewardRate * min(_waitTime, rewardsDuration); + uint256 rewardPerToken = previousRewardPerToken + (newRewards * 1e18 / totalSupply); + uint256 rewardsPerTokenForUser = rewardPerToken - rewardsPerTokenPaid; + uint256 expectedRewards = balance * rewardsPerTokenForUser / (1e18 * 1e12); + + return expectedRewards; + } + function jumpToEndOfRewardsPeriod(uint256 _waitTime) internal { uint256 rewardsDuration = stakingRewardsV1.rewardsDuration();