From 62462f19ac867d0c129ed284bb8783b9f9200d1c Mon Sep 17 00:00:00 2001 From: Flocqst Date: Tue, 2 Jul 2024 16:43:16 +0200 Subject: [PATCH 01/21] =?UTF-8?q?=F0=9F=91=B7=20remove=20staking=20cooldow?= =?UTF-8?q?n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/EscrowMigrator.sol | 10 --------- contracts/RewardEscrowV2.sol | 5 ++--- contracts/StakingRewardsV2.sol | 37 ++-------------------------------- 3 files changed, 4 insertions(+), 48 deletions(-) diff --git a/contracts/EscrowMigrator.sol b/contracts/EscrowMigrator.sol index 943e51b1d..518d1e5b4 100644 --- a/contracts/EscrowMigrator.sol +++ b/contracts/EscrowMigrator.sol @@ -410,7 +410,6 @@ contract EscrowMigrator is _payForMigration(_account); uint256 migratedEscrow; - uint256 cooldown = stakingRewardsV2.cooldownPeriod(); mapping(uint256 => VestingEntry) storage userEntries = registeredVestingSchedules[_account]; for (uint256 i = 0; i < _entryIDs.length; i++) { @@ -432,15 +431,6 @@ contract EscrowMigrator is registeredEntry.migrated = true; migratedEscrow += originalEscrowAmount; - /// @dev it essential for security that the duration is not less than the cooldown period, - /// otherwise the user could do a governance attack by bypassing the unstaking cooldown lock - /// by migrating their escrow then staking, voting, and vesting immediately - if (duration < cooldown) { - uint256 timeCreated = endTime - duration; - duration = cooldown; - endTime = timeCreated + cooldown; - } - IRewardEscrowV2.VestingEntry memory entry = IRewardEscrowV2.VestingEntry({ escrowAmount: originalEscrowAmount, duration: duration, diff --git a/contracts/RewardEscrowV2.sol b/contracts/RewardEscrowV2.sol index f2e02bd68..747507751 100644 --- a/contracts/RewardEscrowV2.sol +++ b/contracts/RewardEscrowV2.sol @@ -379,7 +379,7 @@ contract RewardEscrowV2 is unchecked { amountToUnstake = totalWithFee - unstakedEscrow; } - stakingRewards.unstakeEscrowSkipCooldown(msg.sender, amountToUnstake); + stakingRewards.unstakeEscrowAdmin(msg.sender, amountToUnstake); } // update balances @@ -432,8 +432,7 @@ contract RewardEscrowV2 is if (_earlyVestingFee > MAXIMUM_EARLY_VESTING_FEE) revert EarlyVestingFeeTooHigh(); if (_earlyVestingFee < MINIMUM_EARLY_VESTING_FEE) revert EarlyVestingFeeTooLow(); if (_deposit == 0) revert ZeroAmount(); - uint256 minimumDuration = stakingRewards.cooldownPeriod(); - if (_duration < minimumDuration || _duration > MAX_DURATION) revert InvalidDuration(); + if (_duration == 0 || _duration > MAX_DURATION) revert InvalidDuration(); /// @dev this will revert if the kwenta token transfer fails kwenta.transferFrom(msg.sender, address(this), _deposit); diff --git a/contracts/StakingRewardsV2.sol b/contracts/StakingRewardsV2.sol index 460812c2e..d0007e992 100644 --- a/contracts/StakingRewardsV2.sol +++ b/contracts/StakingRewardsV2.sol @@ -27,12 +27,6 @@ contract StakingRewardsV2 is CONSTANTS/IMMUTABLES ///////////////////////////////////////////////////////////////*/ - /// @notice minimum time length of the unstaking cooldown period - uint256 public constant MIN_COOLDOWN_PERIOD = 1 weeks; - - /// @notice maximum time length of the unstaking cooldown period - uint256 public constant MAX_COOLDOWN_PERIOD = 52 weeks; - /// @notice Contract for KWENTA ERC20 token - used for BOTH staking and rewards /// @custom:oz-upgrades-unsafe-allow state-variable-immutable IKwenta public immutable kwenta; @@ -74,9 +68,6 @@ contract StakingRewardsV2 is /// @notice summation of rewardRate divided by total staked tokens uint256 public rewardPerTokenStored; - /// @inheritdoc IStakingRewardsV2 - uint256 public cooldownPeriod; - /// @notice represents the rewardPerToken /// value the last time the staker calculated earned() rewards mapping(address => uint256) public userRewardPerTokenPaid; @@ -115,17 +106,6 @@ contract StakingRewardsV2 is if (msg.sender != address(rewardsNotifier)) revert OnlyRewardsNotifier(); } - /// @notice only allow execution after the unstaking cooldown period has elapsed - modifier afterCooldown(address _account) { - _afterCooldown(_account); - _; - } - - function _afterCooldown(address _account) internal view { - uint256 canUnstakeAt = userLastStakeTime[_account] + cooldownPeriod; - if (canUnstakeAt > block.timestamp) revert MustWaitForUnlock(canUnstakeAt); - } - /*/////////////////////////////////////////////////////////////// CONSTRUCTOR / INITIALIZER ///////////////////////////////////////////////////////////////*/ @@ -165,7 +145,6 @@ contract StakingRewardsV2 is // define values rewardsDuration = 1 weeks; - cooldownPeriod = 2 weeks; } /*/////////////////////////////////////////////////////////////// @@ -233,7 +212,6 @@ contract StakingRewardsV2 is public whenNotPaused updateReward(msg.sender) - afterCooldown(msg.sender) { if (_amount == 0) revert AmountZero(); uint256 nonEscrowedBalance = nonEscrowedBalanceOf(msg.sender); @@ -278,12 +256,12 @@ contract StakingRewardsV2 is } /// @inheritdoc IStakingRewardsV2 - function unstakeEscrow(uint256 _amount) external afterCooldown(msg.sender) { + function unstakeEscrow(uint256 _amount) external { _unstakeEscrow(msg.sender, _amount); } /// @inheritdoc IStakingRewardsV2 - function unstakeEscrowSkipCooldown(address _account, uint256 _amount) + function unstakeEscrowAdmin(address _account, uint256 _amount) external onlyRewardEscrow { @@ -611,17 +589,6 @@ contract StakingRewardsV2 is emit RewardsDurationUpdated(rewardsDuration); } - /// @inheritdoc IStakingRewardsV2 - function setCooldownPeriod(uint256 _cooldownPeriod) external onlyOwner { - if (_cooldownPeriod < MIN_COOLDOWN_PERIOD) revert CooldownPeriodTooLow(MIN_COOLDOWN_PERIOD); - if (_cooldownPeriod > MAX_COOLDOWN_PERIOD) { - revert CooldownPeriodTooHigh(MAX_COOLDOWN_PERIOD); - } - - cooldownPeriod = _cooldownPeriod; - emit CooldownPeriodUpdated(cooldownPeriod); - } - /*/////////////////////////////////////////////////////////////// PAUSABLE ///////////////////////////////////////////////////////////////*/ From 6006911d8d00c559633b25b27b3a593acf22c3d7 Mon Sep 17 00:00:00 2001 From: Flocqst Date: Tue, 2 Jul 2024 16:46:26 +0200 Subject: [PATCH 02/21] =?UTF-8?q?=F0=9F=91=B7=20update=20interface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/interfaces/IStakingRewardsV2.sol | 27 ++-------------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/contracts/interfaces/IStakingRewardsV2.sol b/contracts/interfaces/IStakingRewardsV2.sol index 2a291640f..41766fbef 100644 --- a/contracts/interfaces/IStakingRewardsV2.sol +++ b/contracts/interfaces/IStakingRewardsV2.sol @@ -59,10 +59,7 @@ interface IStakingRewardsV2 { /// @param _account: address to check /// @return amount of tokens escrowed but not staked function unstakedEscrowedBalanceOf(address _account) external view returns (uint256); - - /// @notice the period of time a user has to wait after staking to unstake - function cooldownPeriod() external view returns (uint256); - + // rewards /// @notice calculate the total rewards for one duration based on the current rate @@ -153,7 +150,7 @@ interface IStakingRewardsV2 { /// @param _account: address of account to unstake from /// @param _amount: amount to unstake /// @dev this function is used to allow tokens to be vested at any time by RewardEscrowV2 - function unstakeEscrowSkipCooldown(address _account, uint256 _amount) external; + function unstakeEscrowAdmin(address _account, uint256 _amount) external; /// @notice unstake all available staked non-escrowed tokens and /// claim any rewards @@ -201,10 +198,6 @@ interface IStakingRewardsV2 { /// @param _rewardsDuration: denoted in seconds function setRewardsDuration(uint256 _rewardsDuration) external; - /// @notice set unstaking cooldown period - /// @param _cooldownPeriod: denoted in seconds - function setCooldownPeriod(uint256 _cooldownPeriod) external; - // pausable /// @dev Triggers stopped state @@ -263,10 +256,6 @@ interface IStakingRewardsV2 { /// @param amount: amount of token recovered event Recovered(address token, uint256 amount); - /// @notice emitted when the unstaking cooldown period is updated - /// @param cooldownPeriod: the new unstaking cooldown period - event CooldownPeriodUpdated(uint256 cooldownPeriod); - /// @notice emitted when an operator is approved /// @param owner: owner of tokens /// @param operator: address of operator @@ -303,21 +292,9 @@ interface IStakingRewardsV2 { /// @notice recovering the staking token is not allowed error CannotRecoverStakingToken(); - /// @notice error when user tries unstake during the cooldown period - /// @param canUnstakeAt timestamp when user can unstake - error MustWaitForUnlock(uint256 canUnstakeAt); - /// @notice error when trying to set a rewards duration that is too short error RewardsDurationCannotBeZero(); - /// @notice error when trying to set a cooldown period below the minimum - /// @param minCooldownPeriod minimum cooldown period - error CooldownPeriodTooLow(uint256 minCooldownPeriod); - - /// @notice error when trying to set a cooldown period above the maximum - /// @param maxCooldownPeriod maximum cooldown period - error CooldownPeriodTooHigh(uint256 maxCooldownPeriod); - /// @notice the caller is not approved to take this action error NotApproved(); From 1052a6df1f6bc0b0f7bb49f22f704fe0694ad762 Mon Sep 17 00:00:00 2001 From: Flocqst Date: Tue, 2 Jul 2024 16:52:39 +0200 Subject: [PATCH 03/21] =?UTF-8?q?=E2=9C=85=20Adapt=20test=20suite=20to=20s?= =?UTF-8?q?taking=20cooldown=20removal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/escrow.migrator.fork.t.sol | 21 +- .../unit/RewardEscrowV2/RewardEscrowV2.t.sol | 14 +- .../RewardEscrowV2Transferability.t.sol | 24 +- .../RewardEscrowV2VestingChanges.t.sol | 6 +- .../StakingRewardsV2/StakingRewardsV2.t.sol | 122 +------ ...StakingRewardsV2OnBehalfActionsTests.t.sol | 6 +- .../StakingV2Checkpointing.t.sol | 49 +-- .../StakingV2CooldownPeriod.t.sol | 303 ------------------ .../helpers/EscrowMigratorTestHelpers.t.sol | 11 +- .../utils/helpers/StakingTestHelpers.t.sol | 5 - 10 files changed, 36 insertions(+), 525 deletions(-) delete mode 100644 test/foundry/unit/StakingRewardsV2/StakingV2CooldownPeriod.t.sol diff --git a/test/foundry/integration/escrow.migrator.fork.t.sol b/test/foundry/integration/escrow.migrator.fork.t.sol index d90d9b40d..629246674 100644 --- a/test/foundry/integration/escrow.migrator.fork.t.sol +++ b/test/foundry/integration/escrow.migrator.fork.t.sol @@ -791,26 +791,7 @@ contract StakingV2MigrationForkTests is EscrowMigratorTestHelpers { // check final state - user2 didn't manage to migrate any entries checkStateAfterStepThree(user1, 0, 0); } - - function test_Cannot_Bypass_Unstaking_Cooldown_Lock() public { - // this is the malicious entry - the duration is set to 1 - createRewardEscrowEntryV1(user1, 50 ether, 1); - - (uint256[] memory _entryIDs, uint256 numVestingEntries,) = claimAndFullyMigrate(user1); - checkStateAfterStepThree(user1, _entryIDs); - - // specifically - uint256[] memory migratedEntryIDs = - rewardEscrowV2.getAccountVestingEntryIDs(user1, numVestingEntries - 2, 1); - uint256 maliciousEntryID = migratedEntryIDs[0]; - (uint256 endTime, uint256 escrowAmount, uint256 duration, uint256 earlyVestingFee) = - rewardEscrowV2.getVestingEntry(maliciousEntryID); - assertEq(endTime, block.timestamp + stakingRewardsV2.cooldownPeriod()); - assertEq(escrowAmount, 50 ether); - assertEq(duration, stakingRewardsV2.cooldownPeriod()); - assertEq(earlyVestingFee, 90); - } - + function test_Cannot_Migrate_In_Non_Initiated_State() public { (uint256[] memory _entryIDs,) = claimAndCheckInitialState(user1); diff --git a/test/foundry/unit/RewardEscrowV2/RewardEscrowV2.t.sol b/test/foundry/unit/RewardEscrowV2/RewardEscrowV2.t.sol index a5996da23..65b4b3c4b 100644 --- a/test/foundry/unit/RewardEscrowV2/RewardEscrowV2.t.sol +++ b/test/foundry/unit/RewardEscrowV2/RewardEscrowV2.t.sol @@ -263,24 +263,12 @@ contract RewardEscrowV2Tests is DefaultStakingV2Setup { rewardEscrowV2.createEscrowEntry(address(this), 0, 52 weeks, 90); } - function test_createEscrowEntry_Should_Not_Append_Entries_With_Short_Duration() public { - uint256 duration = stakingRewardsV2.cooldownPeriod(); - - vm.prank(treasury); - kwenta.approve(address(rewardEscrowV2), TEST_VALUE); - vm.prank(treasury); - vm.expectRevert(IRewardEscrowV2.InvalidDuration.selector); - rewardEscrowV2.createEscrowEntry(address(this), TEST_VALUE, duration - 1, 90); - } - function test_createEscrowEntry_Should_Not_Append_Entries_With_Bad_Duration_Fuzz( uint40 duration ) public { - uint256 cooldownPeriod = stakingRewardsV2.cooldownPeriod(); - vm.prank(treasury); kwenta.approve(address(rewardEscrowV2), TEST_VALUE); - if (duration < cooldownPeriod || duration > rewardEscrowV2.MAX_DURATION()) { + if (duration == 0 || duration > rewardEscrowV2.MAX_DURATION()) { vm.expectRevert(IRewardEscrowV2.InvalidDuration.selector); } vm.prank(treasury); diff --git a/test/foundry/unit/RewardEscrowV2/RewardEscrowV2Transferability.t.sol b/test/foundry/unit/RewardEscrowV2/RewardEscrowV2Transferability.t.sol index 67ac5ddb3..8b5098d3e 100644 --- a/test/foundry/unit/RewardEscrowV2/RewardEscrowV2Transferability.t.sol +++ b/test/foundry/unit/RewardEscrowV2/RewardEscrowV2Transferability.t.sol @@ -41,7 +41,7 @@ contract RewardEscrowV2TransferabilityTests is DefaultStakingV2Setup { function test_Cannot_Steal_Other_Users_Entries_Fuzz(uint32 amount, uint24 duration) public { vm.assume(amount > 0); - vm.assume(duration >= stakingRewardsV2.cooldownPeriod()); + vm.assume(duration > 0); // create the escrow entry createRewardEscrowEntryV2(user1, amount, duration); @@ -61,7 +61,7 @@ contract RewardEscrowV2TransferabilityTests is DefaultStakingV2Setup { uint24 duration ) public { vm.assume(amount > 0); - vm.assume(duration >= stakingRewardsV2.cooldownPeriod()); + vm.assume(duration > 0); // create the escrow entry createRewardEscrowEntryV2(user1, amount, duration); @@ -95,7 +95,7 @@ contract RewardEscrowV2TransferabilityTests is DefaultStakingV2Setup { public { vm.assume(amount > 0); - vm.assume(duration >= stakingRewardsV2.cooldownPeriod()); + vm.assume(duration > 0); // create the escrow entry createRewardEscrowEntryV2(user1, amount, duration); @@ -241,7 +241,7 @@ contract RewardEscrowV2TransferabilityTests is DefaultStakingV2Setup { uint8 numberOfEntries ) public { vm.assume(escrowAmount > 0); - vm.assume(duration >= stakingRewardsV2.cooldownPeriod()); + vm.assume(duration > 0); vm.assume(numberOfEntries > 0); (uint256 totalEscrowedAmount, uint256 user1EntryID) = @@ -285,7 +285,7 @@ contract RewardEscrowV2TransferabilityTests is DefaultStakingV2Setup { ) public { vm.assume(escrowAmount > 0); vm.assume(stakedAmount > 0); - vm.assume(duration >= stakingRewardsV2.cooldownPeriod()); + vm.assume(duration > 0); vm.assume(escrowAmount >= stakedAmount); // create the escrow entry @@ -379,7 +379,7 @@ contract RewardEscrowV2TransferabilityTests is DefaultStakingV2Setup { uint8 numberOfEntries ) public { vm.assume(escrowAmount > 0); - vm.assume(duration >= stakingRewardsV2.cooldownPeriod()); + vm.assume(duration > 0); vm.assume(numberOfEntries > 0); (uint256 totalEscrowedAmount, uint256 user1EntryID) = @@ -423,7 +423,7 @@ contract RewardEscrowV2TransferabilityTests is DefaultStakingV2Setup { ) public { vm.assume(escrowAmount > 0); vm.assume(stakedAmount > 0); - vm.assume(duration >= stakingRewardsV2.cooldownPeriod()); + vm.assume(duration > 0); vm.assume(escrowAmount >= stakedAmount); // create the escrow entry @@ -520,7 +520,7 @@ contract RewardEscrowV2TransferabilityTests is DefaultStakingV2Setup { uint8 numberOfEntries ) public { vm.assume(escrowAmount > 0); - vm.assume(duration >= stakingRewardsV2.cooldownPeriod()); + vm.assume(duration > 0); vm.assume(numberOfEntries > 0); uint256 startingEntryToTransferIndex = @@ -582,7 +582,7 @@ contract RewardEscrowV2TransferabilityTests is DefaultStakingV2Setup { uint8 numberOfEntries ) public { vm.assume(escrowAmount > 0); - vm.assume(duration >= stakingRewardsV2.cooldownPeriod()); + vm.assume(duration > 0); vm.assume(numberOfEntries > 0); // create the escrow entry @@ -673,7 +673,7 @@ contract RewardEscrowV2TransferabilityTests is DefaultStakingV2Setup { vm.assume(escrowAmount > 0); vm.assume(stakedAmount > 0); - vm.assume(duration >= stakingRewardsV2.cooldownPeriod()); + vm.assume(duration > 0); vm.assume(numberOfEntries > 0); vm.assume(escrowAmount * numberOfEntries >= stakedAmount); @@ -746,7 +746,7 @@ contract RewardEscrowV2TransferabilityTests is DefaultStakingV2Setup { uint8 numberOfEntries ) public { vm.assume(escrowAmount > 0); - vm.assume(duration >= stakingRewardsV2.cooldownPeriod()); + vm.assume(duration > 0); vm.assume(numberOfEntries > 0); // create the escrow entries @@ -783,7 +783,7 @@ contract RewardEscrowV2TransferabilityTests is DefaultStakingV2Setup { uint8 numberOfEntries ) public { vm.assume(escrowAmount > 0); - vm.assume(duration >= stakingRewardsV2.cooldownPeriod()); + vm.assume(duration > 0); vm.assume(numberOfEntries > 0); // create the escrow entries diff --git a/test/foundry/unit/RewardEscrowV2/RewardEscrowV2VestingChanges.t.sol b/test/foundry/unit/RewardEscrowV2/RewardEscrowV2VestingChanges.t.sol index 160600090..1e0615895 100644 --- a/test/foundry/unit/RewardEscrowV2/RewardEscrowV2VestingChanges.t.sol +++ b/test/foundry/unit/RewardEscrowV2/RewardEscrowV2VestingChanges.t.sol @@ -48,7 +48,7 @@ contract RewardEscrowV2VestingChangesTests is DefaultStakingV2Setup { uint8 earlyVestingFee ) public { vm.assume(escrowAmount > 0); - vm.assume(duration >= stakingRewardsV2.cooldownPeriod()); + vm.assume(duration > 0); vm.assume(earlyVestingFee <= 100); vm.assume(earlyVestingFee > rewardEscrowV2.MINIMUM_EARLY_VESTING_FEE()); @@ -116,7 +116,7 @@ contract RewardEscrowV2VestingChangesTests is DefaultStakingV2Setup { uint8 earlyVestingFee = _earlyVestingFee; vm.assume(escrowAmount > 0); - vm.assume(duration >= stakingRewardsV2.cooldownPeriod()); + vm.assume(duration > 0); vm.assume(earlyVestingFee <= 100); vm.assume(earlyVestingFee > rewardEscrowV2.MINIMUM_EARLY_VESTING_FEE()); @@ -201,7 +201,7 @@ contract RewardEscrowV2VestingChangesTests is DefaultStakingV2Setup { vm.assume(escrowAmount > 0); vm.assume(stakingAmount > 0); vm.assume(stakingAmount <= escrowAmount); - vm.assume(duration >= stakingRewardsV2.cooldownPeriod()); + vm.assume(duration > 0); vm.assume(earlyVestingFee <= 100); vm.assume(earlyVestingFee > rewardEscrowV2.MINIMUM_EARLY_VESTING_FEE()); diff --git a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2.t.sol b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2.t.sol index 08f9a4626..2f280d7aa 100644 --- a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2.t.sol +++ b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2.t.sol @@ -65,21 +65,9 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { stakingRewardsV2.recoverERC20(address(kwenta), 0); } - function test_Only_RewardEscrowCan_Call_unstakeEscrowSkipCooldown() public { - stakeEscrowedFundsV2(address(this), TEST_VALUE); - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); - vm.expectRevert(IStakingRewardsV2.OnlyRewardEscrow.selector); - stakingRewardsV2.unstakeEscrowSkipCooldown(address(this), TEST_VALUE); - } - function test_Cannot_unstakeEscrow_Invalid_Amount() public { - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); - vm.expectRevert(abi.encodeWithSelector(IStakingRewardsV2.InsufficientBalance.selector, 0)); unstakeEscrowedFundsV2(address(this), TEST_VALUE); - - vm.expectRevert(abi.encodeWithSelector(IStakingRewardsV2.InsufficientBalance.selector, 0)); - unstakeEscrowSkipCooldownFundsV2(address(this), TEST_VALUE); } function test_Only_Owner_Can_Pause_Contract() public { @@ -179,8 +167,6 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { fundAndApproveAccountV2(address(this), TEST_VALUE); stakingRewardsV2.stake(TEST_VALUE); - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); - // pause stakingRewardsV2.pauseStakingRewards(); @@ -240,8 +226,6 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { // fund and stake escrow stakeEscrowedFundsV2(address(this), TEST_VALUE); - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); - // pause stakingRewardsV2.pauseStakingRewards(); @@ -546,7 +530,7 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { ) public { vm.assume(escrowAmount > 0); vm.assume(amountToEscrowStake > escrowAmount); - vm.assume(duration >= stakingRewardsV2.cooldownPeriod()); + vm.assume(duration > 0); createRewardEscrowEntryV2(address(this), escrowAmount, duration); vm.expectRevert( @@ -612,8 +596,6 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { // stake escrow stakeEscrowedFundsV2(address(this), TEST_VALUE); - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); - // this would work if unstakeEscrow was called // but unstake is called so it fails vm.expectRevert(abi.encodeWithSelector(IStakingRewardsV2.InsufficientBalance.selector, 0)); @@ -624,8 +606,6 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { // stake escrow stakeEscrowedFundsV2(address(this), TEST_VALUE); - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); - // exit - this fails because exit uses unstake not unstakeEscrow vm.expectRevert(IStakingRewardsV2.AmountZero.selector); stakingRewardsV2.exit(); @@ -975,8 +955,6 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { //////////////////////////////////////////////////////////////*/ function test_Cannot_unstake_If_Nothing_Staked() public { - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); - vm.expectRevert(abi.encodeWithSelector(IStakingRewardsV2.InsufficientBalance.selector, 0)); stakingRewardsV2.unstake(TEST_VALUE); } @@ -1000,8 +978,6 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { } function test_Cannot_unstake_0() public { - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); - vm.expectRevert(IStakingRewardsV2.AmountZero.selector); stakingRewardsV2.unstake(0); } @@ -1011,13 +987,8 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { //////////////////////////////////////////////////////////////*/ function test_Cannot_unstakeEscrow_If_None_Staked() public { - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); - vm.expectRevert(abi.encodeWithSelector(IStakingRewardsV2.InsufficientBalance.selector, 0)); unstakeEscrowedFundsV2(address(this), TEST_VALUE); - - vm.expectRevert(abi.encodeWithSelector(IStakingRewardsV2.InsufficientBalance.selector, 0)); - unstakeEscrowSkipCooldownFundsV2(address(this), TEST_VALUE); } function test_unstakeEscrow_Does_Not_Change_Token_Balances() public { @@ -1027,9 +998,6 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { uint256 initialTokenBalance = kwenta.balanceOf(address(this)); uint256 initialEscrowTokenBalance = kwenta.balanceOf(address(rewardEscrowV2)); - // pass cooldown period - vm.warp(block.timestamp + 2 weeks); - // unstake escrow unstakeEscrowedFundsV2(address(this), 1 weeks); @@ -1041,33 +1009,12 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { assertEq(initialEscrowTokenBalance, finalEscrowTokenBalance); } - function test_unstakeEscrowSkipCooldown_Does_Not_Change_Token_Balances() public { - // stake escrow - stakeEscrowedFundsV2(address(this), 1 weeks); - - uint256 initialTokenBalance = kwenta.balanceOf(address(this)); - uint256 initialEscrowTokenBalance = kwenta.balanceOf(address(rewardEscrowV2)); - - // unstake escrow - unstakeEscrowSkipCooldownFundsV2(address(this), 1 weeks); - - uint256 finalTokenBalance = kwenta.balanceOf(address(this)); - uint256 finalEscrowTokenBalance = kwenta.balanceOf(address(rewardEscrowV2)); - - // check both values unchanged - assertEq(initialTokenBalance, finalTokenBalance); - assertEq(initialEscrowTokenBalance, finalEscrowTokenBalance); - } - function test_unstakeEscrow_Does_Change_totalSupply() public { // stake escrow stakeEscrowedFundsV2(address(this), 1 weeks); uint256 initialTotalSupply = stakingRewardsV2.totalSupply(); - // pass cooldown period - vm.warp(block.timestamp + 2 weeks); - // unstake escrow unstakeEscrowedFundsV2(address(this), 1 weeks); @@ -1077,30 +1024,12 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { assertEq(initialTotalSupply - 1 weeks, finalTotalSupply); } - function test_unstakeEscrowSkipCooldown_Does_Change_totalSupply() public { - // stake escrow - stakeEscrowedFundsV2(address(this), 1 weeks); - - uint256 initialTotalSupply = stakingRewardsV2.totalSupply(); - - // unstake escrow - unstakeEscrowSkipCooldownFundsV2(address(this), 1 weeks); - - uint256 finalTotalSupply = stakingRewardsV2.totalSupply(); - - // check total supply decreased - assertEq(initialTotalSupply - 1 weeks, finalTotalSupply); - } - function test_unstakeEscrow_Does_Change_Balances_Mapping() public { // stake escrow stakeEscrowedFundsV2(address(this), 1 weeks); uint256 initialBalance = stakingRewardsV2.balanceOf(address(this)); - // pass cooldown period - vm.warp(block.timestamp + 2 weeks); - // unstake escrow unstakeEscrowedFundsV2(address(this), 1 weeks); @@ -1110,30 +1039,12 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { assertEq(initialBalance - 1 weeks, finalBalance); } - function test_unstakeEscrowSkipCooldown_Does_Change_Balances_Mapping() public { - // stake escrow - stakeEscrowedFundsV2(address(this), 1 weeks); - - uint256 initialBalance = stakingRewardsV2.balanceOf(address(this)); - - // unstake escrow - unstakeEscrowSkipCooldownFundsV2(address(this), 1 weeks); - - uint256 finalBalance = stakingRewardsV2.balanceOf(address(this)); - - // check balance decreased - assertEq(initialBalance - 1 weeks, finalBalance); - } - function test_unstakeEscrow_Does_Change_Escrowed_Balances_Mapping() public { // stake escrow stakeEscrowedFundsV2(address(this), 1 weeks); uint256 initialEscrowBalance = stakingRewardsV2.escrowedBalanceOf(address(this)); - // pass cooldown period - vm.warp(block.timestamp + 2 weeks); - // unstake escrow unstakeEscrowedFundsV2(address(this), 1 weeks); @@ -1142,54 +1053,25 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { // check balance decreased assertEq(initialEscrowBalance - 1 weeks, finalEscrowBalance); } - - function test_unstakeEscrowSkipCooldown_Does_Change_Escrowed_Balances_Mapping() public { - // stake escrow - stakeEscrowedFundsV2(address(this), 1 weeks); - - uint256 initialEscrowBalance = stakingRewardsV2.escrowedBalanceOf(address(this)); - - // unstake escrow - unstakeEscrowSkipCooldownFundsV2(address(this), 1 weeks); - - uint256 finalEscrowBalance = stakingRewardsV2.escrowedBalanceOf(address(this)); - - // check balance decreased - assertEq(initialEscrowBalance - 1 weeks, finalEscrowBalance); - } - + function test_Cannot_unstakeEscrow_More_Than_Escrow_Staked() public { // stake escrow stakeEscrowedFundsV2(address(this), 1 weeks); - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); - // unstake more escrow vm.expectRevert( abi.encodeWithSelector(IStakingRewardsV2.InsufficientBalance.selector, 1 weeks) ); unstakeEscrowedFundsV2(address(this), 2 weeks); - - // unstake more escrow - vm.expectRevert( - abi.encodeWithSelector(IStakingRewardsV2.InsufficientBalance.selector, 1 weeks) - ); - unstakeEscrowSkipCooldownFundsV2(address(this), 2 weeks); } function test_Cannot_unstakeEscrow_0() public { // stake escrow stakeEscrowedFundsV2(address(this), 1 weeks); - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); - // unstake 0 escrow vm.expectRevert(IStakingRewardsV2.AmountZero.selector); unstakeEscrowedFundsV2(address(this), 0); - - // unstake 0 escrow - vm.expectRevert(IStakingRewardsV2.AmountZero.selector); - unstakeEscrowSkipCooldownFundsV2(address(this), 0); } /*////////////////////////////////////////////////////////////// diff --git a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2OnBehalfActionsTests.t.sol b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2OnBehalfActionsTests.t.sol index a87e1f19c..3dc42efbf 100644 --- a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2OnBehalfActionsTests.t.sol +++ b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2OnBehalfActionsTests.t.sol @@ -90,7 +90,7 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { address caller ) public { vm.assume(escrowAmount > 0); - vm.assume(duration >= stakingRewardsV2.cooldownPeriod()); + vm.assume(duration > 0); vm.assume(owner != address(0)); vm.assume(operator != address(0)); vm.assume(caller != address(0)); @@ -271,7 +271,7 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { uint24 duration ) public { vm.assume(escrowAmount > 0); - vm.assume(duration >= stakingRewardsV2.cooldownPeriod()); + vm.assume(duration > 0); vm.assume(owner != address(0)); vm.assume(operator != address(0)); vm.assume(owner != operator); @@ -330,7 +330,7 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { ) public { vm.assume(escrowAmount > 0); vm.assume(amountToEscrowStake > escrowAmount); - vm.assume(duration >= stakingRewardsV2.cooldownPeriod()); + vm.assume(duration > 0); vm.assume(owner != address(0)); vm.assume(operator != address(0)); vm.assume(owner != operator); diff --git a/test/foundry/unit/StakingRewardsV2/StakingV2Checkpointing.t.sol b/test/foundry/unit/StakingRewardsV2/StakingV2Checkpointing.t.sol index 97920dd68..c9c88a318 100644 --- a/test/foundry/unit/StakingRewardsV2/StakingV2Checkpointing.t.sol +++ b/test/foundry/unit/StakingRewardsV2/StakingV2Checkpointing.t.sol @@ -23,9 +23,6 @@ contract StakingV2CheckpointingTests is DefaultStakingV2Setup { assertEq(blk, block.number); assertEq(value, TEST_VALUE); - // move beyond cold period - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); - // update block timestamp vm.warp(block.timestamp + 1); vm.roll(block.number + 1); @@ -55,9 +52,6 @@ contract StakingV2CheckpointingTests is DefaultStakingV2Setup { assertEq(blk, block.number); assertEq(value, TEST_VALUE * 2); - // move beyond cold period - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); - // update block timestamp vm.warp(block.timestamp + 1); vm.roll(block.number + 1); @@ -88,9 +82,6 @@ contract StakingV2CheckpointingTests is DefaultStakingV2Setup { assertEq(blk, block.number); assertEq(value, TEST_VALUE); - // move beyond cold period - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); - // update block timestamp vm.warp(block.timestamp + 1); vm.roll(block.number + 1); @@ -120,9 +111,6 @@ contract StakingV2CheckpointingTests is DefaultStakingV2Setup { assertEq(blk, block.number); assertEq(value, TEST_VALUE * 2); - // move beyond cold period - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); - // update block timestamp vm.warp(block.timestamp + 1); vm.roll(block.number + 1); @@ -173,7 +161,7 @@ contract StakingV2CheckpointingTests is DefaultStakingV2Setup { assertEq(value, previousTotal + amountToStake); // move beyond cold period - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); + vm.warp(block.timestamp + 2 weeks); // update block timestamp vm.warp(block.timestamp + timestampAdvance); @@ -229,7 +217,7 @@ contract StakingV2CheckpointingTests is DefaultStakingV2Setup { assertEq(value, previousTotal + amountToStake); // move beyond cold period - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); + vm.warp(block.timestamp + 2 weeks); // update block timestamp vm.warp(block.timestamp + timestampAdvance); @@ -269,9 +257,6 @@ contract StakingV2CheckpointingTests is DefaultStakingV2Setup { assertEq(blk, block.number); assertEq(value, TEST_VALUE); - // move beyond cold period - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); - // update block timestamp vm.warp(block.timestamp + 1); vm.roll(block.number + 1); @@ -301,9 +286,6 @@ contract StakingV2CheckpointingTests is DefaultStakingV2Setup { assertEq(blk, block.number); assertEq(value, TEST_VALUE * 2); - // move beyond cold period - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); - // update block timestamp vm.warp(block.timestamp + 1); vm.roll(block.number + 1); @@ -354,7 +336,7 @@ contract StakingV2CheckpointingTests is DefaultStakingV2Setup { assertEq(value, previousTotal + amountToStake); // move beyond cold period - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); + vm.warp(block.timestamp + 2 weeks); // update block timestamp vm.warp(block.timestamp + timestampAdvance); @@ -393,9 +375,6 @@ contract StakingV2CheckpointingTests is DefaultStakingV2Setup { assertEq(blk, block.number); assertEq(value, TEST_VALUE); - // move beyond cold period - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); - // update block timestamp vm.warp(block.timestamp + 1); vm.roll(block.number + 1); @@ -424,9 +403,6 @@ contract StakingV2CheckpointingTests is DefaultStakingV2Setup { assertEq(blk, block.number); assertEq(value, TEST_VALUE * 2); - // move beyond cold period - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); - // update block timestamp vm.warp(block.timestamp + 1); vm.roll(block.number + 1); @@ -456,9 +432,6 @@ contract StakingV2CheckpointingTests is DefaultStakingV2Setup { assertEq(blk, block.number); assertEq(value, TEST_VALUE); - // move beyond cold period - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); - // update block timestamp vm.warp(block.timestamp + 1); vm.roll(block.number + 1); @@ -513,7 +486,7 @@ contract StakingV2CheckpointingTests is DefaultStakingV2Setup { assertEq(value, previousTotal + amountToStake); // move beyond cold period - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); + vm.warp(block.timestamp + 2 weeks); // update block timestamp vm.warp(block.timestamp + timestampAdvance); @@ -580,7 +553,7 @@ contract StakingV2CheckpointingTests is DefaultStakingV2Setup { expectedValue = totalStaked; } fundAccountAndStakeV2(address(this), amount); - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); + vm.warp(block.timestamp + 1); } uint256 value = stakingRewardsV2.balanceAtTime(address(this), timestampToFind); @@ -604,7 +577,7 @@ contract StakingV2CheckpointingTests is DefaultStakingV2Setup { expectedValue = totalStaked; } fundAccountAndStakeV2(address(this), amount); - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); + vm.warp(block.timestamp + 1); } uint256 value = stakingRewardsV2.balanceAtTime(address(this), timestampToFind); @@ -622,14 +595,14 @@ contract StakingV2CheckpointingTests is DefaultStakingV2Setup { totalStaked += amount; fundAccountAndStakeV2(address(this), amount); if (block.timestamp == xCooldownPeriods(2)) { - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); + vm.warp(block.timestamp + 1); stakingRewardsV2.unstake(amount); stakingRewardsV2.unstake(amount); totalStaked -= amount; totalStaked -= amount; } if (block.timestamp == xCooldownPeriods(5)) { - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); + vm.warp(block.timestamp + 1); stakingRewardsV2.unstake(amount); stakingRewardsV2.unstake(amount); stakingRewardsV2.unstake(amount); @@ -640,7 +613,7 @@ contract StakingV2CheckpointingTests is DefaultStakingV2Setup { if (timestampToFind == block.timestamp) { expectedValue = totalStaked; } - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod()); + vm.warp(block.timestamp + 1); } uint256 value = stakingRewardsV2.balanceAtTime(address(this), timestampToFind); @@ -957,7 +930,7 @@ contract StakingV2CheckpointingTests is DefaultStakingV2Setup { Helpers //////////////////////////////////////////////////////////////*/ - function xCooldownPeriods(uint256 numCooldowns) public view returns (uint256) { - return 1 + (numCooldowns * stakingRewardsV2.cooldownPeriod()); + function xCooldownPeriods(uint256 numCooldowns) public pure returns (uint256) { + return 1 + (numCooldowns * 1); } } diff --git a/test/foundry/unit/StakingRewardsV2/StakingV2CooldownPeriod.t.sol b/test/foundry/unit/StakingRewardsV2/StakingV2CooldownPeriod.t.sol deleted file mode 100644 index b4aaa2a0d..000000000 --- a/test/foundry/unit/StakingRewardsV2/StakingV2CooldownPeriod.t.sol +++ /dev/null @@ -1,303 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.19; - -import {console} from "forge-std/Test.sol"; -import {DefaultStakingV2Setup} from "../../utils/setup/DefaultStakingV2Setup.t.sol"; -import {IStakingRewardsV2} from "../../../../contracts/interfaces/IStakingRewardsV2.sol"; -import "../../utils/Constants.t.sol"; - -contract StakingV2CooldownPeriodTests is DefaultStakingV2Setup { - /*////////////////////////////////////////////////////////////// - Unstaking During Cooldown - //////////////////////////////////////////////////////////////*/ - - function test_Cannot_unstake_During_Cooldown() public { - // stake - fundAccountAndStakeV2(address(this), TEST_VALUE); - - uint256 cooldownPeriod = stakingRewardsV2.cooldownPeriod(); - uint256 canUnstakeAt = block.timestamp + cooldownPeriod; - uint256 stakedAt = block.timestamp; - - // unstake immediately - vm.expectRevert( - abi.encodeWithSelector(IStakingRewardsV2.MustWaitForUnlock.selector, canUnstakeAt) - ); - stakingRewardsV2.unstake(TEST_VALUE); - - // unstake midway through - vm.warp(stakedAt + cooldownPeriod / 2); - vm.expectRevert( - abi.encodeWithSelector(IStakingRewardsV2.MustWaitForUnlock.selector, canUnstakeAt) - ); - stakingRewardsV2.unstake(TEST_VALUE); - - // unstake 1 sec before period ends - vm.warp(stakedAt + cooldownPeriod - 1); - vm.expectRevert( - abi.encodeWithSelector(IStakingRewardsV2.MustWaitForUnlock.selector, canUnstakeAt) - ); - stakingRewardsV2.unstake(TEST_VALUE); - } - - function test_Cannot_unstake_During_Cooldown_Fuzz(uint32 stakeAmount, uint32 waitTime) public { - vm.assume(stakeAmount > 0); - - // stake - fundAccountAndStakeV2(address(this), stakeAmount); - - uint256 cooldownPeriod = stakingRewardsV2.cooldownPeriod(); - uint256 canUnstakeAt = block.timestamp + cooldownPeriod; - uint256 stakedAt = block.timestamp; - - // unstake - vm.warp(stakedAt + waitTime); - if (waitTime < cooldownPeriod) { - vm.expectRevert( - abi.encodeWithSelector(IStakingRewardsV2.MustWaitForUnlock.selector, canUnstakeAt) - ); - } - stakingRewardsV2.unstake(stakeAmount); - } - - function test_Cannot_unstakeEscrow_During_Cooldown() public { - // stake - stakeEscrowedFundsV2(address(this), TEST_VALUE); - - uint256 cooldownPeriod = stakingRewardsV2.cooldownPeriod(); - uint256 canUnstakeAt = block.timestamp + cooldownPeriod; - uint256 stakedAt = block.timestamp; - - // unstake immediately - vm.expectRevert( - abi.encodeWithSelector(IStakingRewardsV2.MustWaitForUnlock.selector, canUnstakeAt) - ); - unstakeEscrowedFundsV2(address(this), TEST_VALUE); - - // unstake midway through - vm.warp(stakedAt + cooldownPeriod / 2); - vm.expectRevert( - abi.encodeWithSelector(IStakingRewardsV2.MustWaitForUnlock.selector, canUnstakeAt) - ); - unstakeEscrowedFundsV2(address(this), TEST_VALUE); - - // unstake 1 sec before period ends - vm.warp(stakedAt + cooldownPeriod - 1); - vm.expectRevert( - abi.encodeWithSelector(IStakingRewardsV2.MustWaitForUnlock.selector, canUnstakeAt) - ); - unstakeEscrowedFundsV2(address(this), TEST_VALUE); - } - - function test_Cannot_unstakeEscrow_During_Cooldown_Fuzz(uint32 stakeAmount, uint32 waitTime) - public - { - vm.assume(stakeAmount > 0); - - // stake - stakeEscrowedFundsV2(address(this), stakeAmount); - - uint256 cooldownPeriod = stakingRewardsV2.cooldownPeriod(); - uint256 canUnstakeAt = block.timestamp + cooldownPeriod; - uint256 stakedAt = block.timestamp; - - // unstake - vm.warp(stakedAt + waitTime); - if (waitTime < cooldownPeriod) { - vm.expectRevert( - abi.encodeWithSelector(IStakingRewardsV2.MustWaitForUnlock.selector, canUnstakeAt) - ); - } - unstakeEscrowedFundsV2(address(this), stakeAmount); - } - - /*////////////////////////////////////////////////////////////// - Staking During Cooldown - //////////////////////////////////////////////////////////////*/ - - function test_Can_stake_More_During_Cooldown() public { - // stake once - fundAndApproveAccountV2(address(this), TEST_VALUE * 3); - stakingRewardsV2.stake(TEST_VALUE); - - // stake immediately again - stakingRewardsV2.stake(TEST_VALUE); - - // stake half the cooldown period later again - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod() / 2); - stakingRewardsV2.stake(TEST_VALUE); - } - - function test_Can_stakeEscrow_More_During_Cooldown() public { - // stake once - stakeEscrowedFundsV2(address(this), TEST_VALUE); - - // stake immediately again - stakeEscrowedFundsV2(address(this), TEST_VALUE); - - // stake half the cooldown period later again - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod() / 2); - stakeEscrowedFundsV2(address(this), TEST_VALUE); - } - - function test_Staking_During_Cooldown_Extends_Wait() public { - // stake - fundAndApproveAccountV2(address(this), TEST_VALUE * 3); - stakingRewardsV2.stake(TEST_VALUE); - - // stake half the cooldown period later again - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod() / 2); - stakingRewardsV2.stake(TEST_VALUE); - - // expected can unstakeAt time is now the cooldown period from now - uint256 canUnstakeAt = block.timestamp + stakingRewardsV2.cooldownPeriod(); - - // cannot unstake another half period later - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod() / 2); - vm.expectRevert( - abi.encodeWithSelector(IStakingRewardsV2.MustWaitForUnlock.selector, canUnstakeAt) - ); - stakingRewardsV2.unstake(TEST_VALUE); - } - - function test_Staking_Escrow_During_Cooldown_Extends_Wait() public { - // stake - stakeEscrowedFundsV2(address(this), TEST_VALUE); - - // stake half the cooldown period later again - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod() / 2); - stakeEscrowedFundsV2(address(this), TEST_VALUE); - - // expected can unstakeAt time is now the cooldown period from now - uint256 canUnstakeAt = block.timestamp + stakingRewardsV2.cooldownPeriod(); - - // cannot unstake another half period later - vm.warp(block.timestamp + stakingRewardsV2.cooldownPeriod() / 2); - vm.expectRevert( - abi.encodeWithSelector(IStakingRewardsV2.MustWaitForUnlock.selector, canUnstakeAt) - ); - unstakeEscrowedFundsV2(address(this), TEST_VALUE); - } - - /*////////////////////////////////////////////////////////////// - Changing Cooldown Period - //////////////////////////////////////////////////////////////*/ - - function test_setCooldownPeriod_Is_Only_Owner() public { - vm.expectRevert("Ownable: caller is not the owner"); - vm.prank(user1); - stakingRewardsV2.setCooldownPeriod(1 weeks); - } - - function test_setCooldownPeriod() public { - uint256 newCooldownPeriod = 1 weeks; - - // Expect correct event emitted - vm.expectEmit(true, false, false, true); - emit CooldownPeriodUpdated(newCooldownPeriod); - - // Set new cooldown period - stakingRewardsV2.setCooldownPeriod(newCooldownPeriod); - - // Check cooldown period is updated - assertEq(stakingRewardsV2.cooldownPeriod(), newCooldownPeriod); - - // stake - fundAccountAndStakeV2(address(this), TEST_VALUE); - - // stake escrow - stakeEscrowedFundsV2(address(this), TEST_VALUE); - - // move forward new cooldown period - vm.warp(block.timestamp + newCooldownPeriod); - - // unstake - stakingRewardsV2.unstake(TEST_VALUE); - - // unstake escrow - unstakeEscrowedFundsV2(address(this), TEST_VALUE); - } - - function test_setCooldownPeriod_Fuzz(uint128 newCooldownPeriod, uint128 timeJump) public { - vm.assume(newCooldownPeriod > stakingRewardsV2.MIN_COOLDOWN_PERIOD()); - vm.assume(newCooldownPeriod < stakingRewardsV2.MAX_COOLDOWN_PERIOD()); - - // Expect correct event emitted - vm.expectEmit(true, false, false, true); - emit CooldownPeriodUpdated(newCooldownPeriod); - - // Set new cooldown period - stakingRewardsV2.setCooldownPeriod(newCooldownPeriod); - - // Check cooldown period is updated - assertEq(stakingRewardsV2.cooldownPeriod(), newCooldownPeriod); - - // stake - fundAccountAndStakeV2(address(this), TEST_VALUE); - - // stake escrow - stakeEscrowedFundsV2(address(this), TEST_VALUE); - - uint256 canUnstakeAt = block.timestamp + newCooldownPeriod; - - // move forward new cooldown period - vm.warp(block.timestamp + timeJump); - - if (timeJump < newCooldownPeriod) { - // Expect revert if unstaking before cooldown period - vm.expectRevert( - abi.encodeWithSelector(IStakingRewardsV2.MustWaitForUnlock.selector, canUnstakeAt) - ); - } - // unstake - stakingRewardsV2.unstake(TEST_VALUE); - - if (timeJump < newCooldownPeriod) { - // Expect revert if unstaking before cooldown period - vm.expectRevert( - abi.encodeWithSelector(IStakingRewardsV2.MustWaitForUnlock.selector, canUnstakeAt) - ); - } - // unstake escrow - unstakeEscrowedFundsV2(address(this), TEST_VALUE); - } - - function test_setCooldownPeriod_Range() public { - // Expect revert if cooldown period is too low - uint256 minPeriod = stakingRewardsV2.MIN_COOLDOWN_PERIOD(); - vm.expectRevert( - abi.encodeWithSelector(IStakingRewardsV2.CooldownPeriodTooLow.selector, minPeriod) - ); - stakingRewardsV2.setCooldownPeriod(minPeriod - 1); - - // Expect revert if cooldown period is too high - uint256 maxPeriod = stakingRewardsV2.MAX_COOLDOWN_PERIOD(); - vm.expectRevert( - abi.encodeWithSelector(IStakingRewardsV2.CooldownPeriodTooHigh.selector, maxPeriod) - ); - stakingRewardsV2.setCooldownPeriod(maxPeriod + 1); - } - - function test_setCooldownPeriod_Range_Fuzz(uint256 newCooldownPeriod) public { - // Expect revert if cooldown period is too low - uint256 minPeriod = stakingRewardsV2.MIN_COOLDOWN_PERIOD(); - if (newCooldownPeriod < minPeriod) { - vm.expectRevert( - abi.encodeWithSelector(IStakingRewardsV2.CooldownPeriodTooLow.selector, minPeriod) - ); - } else { - // Expect revert if cooldown period is too high - uint256 maxPeriod = stakingRewardsV2.MAX_COOLDOWN_PERIOD(); - if (newCooldownPeriod > maxPeriod) { - vm.expectRevert( - abi.encodeWithSelector( - IStakingRewardsV2.CooldownPeriodTooHigh.selector, maxPeriod - ) - ); - } - } - - // Set new cooldown period - stakingRewardsV2.setCooldownPeriod(newCooldownPeriod); - } -} diff --git a/test/foundry/utils/helpers/EscrowMigratorTestHelpers.t.sol b/test/foundry/utils/helpers/EscrowMigratorTestHelpers.t.sol index ad9b48b55..1f7eb9e99 100644 --- a/test/foundry/utils/helpers/EscrowMigratorTestHelpers.t.sol +++ b/test/foundry/utils/helpers/EscrowMigratorTestHelpers.t.sol @@ -504,14 +504,9 @@ contract EscrowMigratorTestHelpers is StakingTestHelpers { assertEq(earlyVestingFee, 90); assertEq(escrowAmount, registeredEscrowAmount); - uint256 cooldown = stakingRewardsV2.cooldownPeriod(); - if (registeredDuration < cooldown) { - assertEq(duration, cooldown); - assertEq(endTime, registeredEndTime - registeredDuration + cooldown); - } else { - assertEq(duration, registeredDuration); - assertEq(endTime, registeredEndTime); - } + assertEq(duration, registeredDuration); + assertEq(endTime, registeredEndTime); + } function checkEntryAfterStepThree(address account, uint256 i, uint256 entryID) diff --git a/test/foundry/utils/helpers/StakingTestHelpers.t.sol b/test/foundry/utils/helpers/StakingTestHelpers.t.sol index b2cb42f15..f2c052884 100644 --- a/test/foundry/utils/helpers/StakingTestHelpers.t.sol +++ b/test/foundry/utils/helpers/StakingTestHelpers.t.sol @@ -205,11 +205,6 @@ contract StakingTestHelpers is StakingV2Setup { stakingRewardsV2.unstakeEscrow(_amount); } - function unstakeEscrowSkipCooldownFundsV2(address _account, uint256 _amount) internal { - vm.prank(address(rewardEscrowV2)); - stakingRewardsV2.unstakeEscrowSkipCooldown(_account, _amount); - } - function bulkCreateV1EscrowEntries(address _account, uint256 _amount, uint256 num) internal { vm.startPrank(treasury); kwenta.approve(address(rewardEscrowV1), _amount * num); From 5142e9028afc4e303e671f1cbe04a167b6e9c56d Mon Sep 17 00:00:00 2001 From: Flocqst Date: Tue, 2 Jul 2024 17:09:37 +0200 Subject: [PATCH 04/21] =?UTF-8?q?=F0=9F=93=9A=20update=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/interfaces/IStakingRewardsV2.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/interfaces/IStakingRewardsV2.sol b/contracts/interfaces/IStakingRewardsV2.sol index 41766fbef..4bdaedb51 100644 --- a/contracts/interfaces/IStakingRewardsV2.sol +++ b/contracts/interfaces/IStakingRewardsV2.sol @@ -146,7 +146,7 @@ interface IStakingRewardsV2 { /// @dev updateReward() called prior to function logic function unstakeEscrow(uint256 _amount) external; - /// @notice unstake escrowed token skipping the cooldown wait period + /// @notice unstake escrowed token on behalf of another account /// @param _account: address of account to unstake from /// @param _amount: amount to unstake /// @dev this function is used to allow tokens to be vested at any time by RewardEscrowV2 From 173586705142f8a5d78007db8a9336e16ab391a0 Mon Sep 17 00:00:00 2001 From: Flocqst Date: Fri, 5 Jul 2024 17:00:22 +0200 Subject: [PATCH 05/21] =?UTF-8?q?=F0=9F=91=B7=20Remove=20escrow=20on=20inf?= =?UTF-8?q?lationary=20rewards?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/StakingRewardsV2.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/StakingRewardsV2.sol b/contracts/StakingRewardsV2.sol index 460812c2e..506abfb95 100644 --- a/contracts/StakingRewardsV2.sol +++ b/contracts/StakingRewardsV2.sol @@ -137,7 +137,8 @@ contract StakingRewardsV2 is /// @param _rewardEscrow The address for the RewardEscrowV2 contract /// @param _rewardsNotifier The address for the StakingRewardsNotifier contract constructor(address _kwenta, address _rewardEscrow, address _rewardsNotifier) { - if (_kwenta == address(0) || _rewardEscrow == address(0) || _rewardsNotifier == address(0)) { + if (_kwenta == address(0) || _rewardEscrow == address(0) || _rewardsNotifier == address(0)) + { revert ZeroAddress(); } @@ -343,10 +344,9 @@ contract StakingRewardsV2 is // emit reward claimed event and index account emit RewardPaid(_account, reward); - // transfer token from this contract to the rewardEscrow - // and create a vesting entry at the _to address - kwenta.transfer(address(rewardEscrow), reward); - rewardEscrow.appendVestingEntry(_to, reward); + // transfer token from this contract to the account + // as newly issued rewards from inflation are now issued as non-escrowed + kwenta.transfer(_to, reward); } } @@ -502,7 +502,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 From 43300af9a6e1cabeb8fbb544a1914175352442fc Mon Sep 17 00:00:00 2001 From: Flocqst Date: Mon, 8 Jul 2024 17:09:21 +0200 Subject: [PATCH 06/21] =?UTF-8?q?=F0=9F=93=9A=20@custom:todo=20adjust=20te?= =?UTF-8?q?sts=20to=20non=20escrowed=20rewards?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/foundry/integration/stakingV2.migration.t.sol | 2 ++ test/foundry/integration/stakingV2.upgrade.t.sol | 5 +++++ test/foundry/unit/StakingRewardsV2/StakingRewardsV2.t.sol | 3 +++ .../unit/StakingRewardsV2/StakingRewardsV2Compound.t.sol | 3 +++ .../StakingRewardsV2OnBehalfActionsTests.t.sol | 6 ++++++ .../unit/StakingRewardsV2/StakingV2RewardCalculations.t.sol | 6 ++++++ 6 files changed, 25 insertions(+) diff --git a/test/foundry/integration/stakingV2.migration.t.sol b/test/foundry/integration/stakingV2.migration.t.sol index 224a595fa..322489d87 100644 --- a/test/foundry/integration/stakingV2.migration.t.sol +++ b/test/foundry/integration/stakingV2.migration.t.sol @@ -10,6 +10,7 @@ contract StakingV2MigrationTests is StakingTestHelpers { Migration Tests //////////////////////////////////////////////////////////////*/ + // @custom:todo FAIL. Reason: AmountZero() function test_Migrate_Then_Move_Funds_From_V1_To_V2_And_Generate_New_Rewards() public { // Stake tokens in StakingV1 fundAccountAndStakeV1(user1, 10 ether); @@ -151,6 +152,7 @@ contract StakingV2MigrationTests is StakingTestHelpers { assertEq(rewardEscrowV2.escrowedBalanceOf(user3), user3EscrowStakedV2); } + // @custom:todo FAIL. Reason: AmountZero() function test_Migrate_Then_Move_Funds_From_V1_To_V2_And_Generate_New_Rewards_Fuzz( uint32 maxFundingAmount, uint8 numberOfStakers diff --git a/test/foundry/integration/stakingV2.upgrade.t.sol b/test/foundry/integration/stakingV2.upgrade.t.sol index adbcbed31..748e08d7b 100644 --- a/test/foundry/integration/stakingV2.upgrade.t.sol +++ b/test/foundry/integration/stakingV2.upgrade.t.sol @@ -92,6 +92,7 @@ contract StakingV2UpgradeTests is DefaultStakingV2Setup { Upgrade StakingRewardsV2 //////////////////////////////////////////////////////////////*/ + // @custom:todo FAIL. Reason: assertion failed function test_Upgrade_StakingRewardsV2_To_V3() public { address stakingRewardsV3Implementation = deployStakingRewardsV3Implementation(); @@ -105,6 +106,7 @@ contract StakingV2UpgradeTests is DefaultStakingV2Setup { testStakingV2StillWorking(); } + // @custom:todo FAIL. Reason: assertion failed function test_Upgrade_And_Call_StakingRewardsV2_To_V3() public { address stakingRewardsV3Implementation = deployStakingRewardsV3Implementation(); @@ -124,6 +126,7 @@ contract StakingV2UpgradeTests is DefaultStakingV2Setup { Upgrade RewardEscrowV2 //////////////////////////////////////////////////////////////*/ + // @custom:todo FAIL. Reason: assertion failed function test_Upgrade_RewardEscrowV2_To_V3() public { address rewardEscrowV3Implementation = address(new MockRewardEscrowV3(address(kwenta), address(0x1))); @@ -138,6 +141,7 @@ contract StakingV2UpgradeTests is DefaultStakingV2Setup { testStakingV2StillWorking(); } + // @custom:todo FAIL. Reason: assertion failed function test_Upgrade_And_Call_RewardEscrowV2_To_V3() public { address rewardEscrowV3Implementation = address(new MockRewardEscrowV3(address(kwenta), address(0x1))); @@ -158,6 +162,7 @@ contract StakingV2UpgradeTests is DefaultStakingV2Setup { UPGRADE ESCROW MIGRATOR //////////////////////////////////////////////////////////////*/ + // @custom:todo FAIL. Reason: assertion failed function test_Upgrade_EscrowMigrator_To_V2() public { address escrowMigratorV2Impl = deployEscrowMigratorImpl(); diff --git a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2.t.sol b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2.t.sol index 08f9a4626..5d22aa9a5 100644 --- a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2.t.sol +++ b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2.t.sol @@ -313,6 +313,7 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { stakingRewardsV2.getRewardOnBehalf(address(this)); } + // @custom:todo FAIL. Reason: AmountZero() function test_Cannot_Compound_When_Paused() public { // fund and stake fundAndApproveAccountV2(address(this), TEST_VALUE); @@ -339,6 +340,7 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { stakingRewardsV2.compound(); } + // @custom:todo FAIL. Reason: AmountZero() function test_Cannot_Compound_On_Behalf_When_Paused() public { // approve operator stakingRewardsV2.approveOperator(user1, true); @@ -738,6 +740,7 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { getReward //////////////////////////////////////////////////////////////*/ + // @custom:todo FAIL; Reason: assertion failed function test_getReward_Increases_Balance_In_Escrow() public { fundAndApproveAccountV2(address(this), TEST_VALUE); diff --git a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2Compound.t.sol b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2Compound.t.sol index b3690eac4..483140adc 100644 --- a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2Compound.t.sol +++ b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2Compound.t.sol @@ -11,6 +11,7 @@ contract StakingRewardsV2CompoundTests is DefaultStakingV2Setup { Compound Function //////////////////////////////////////////////////////////////*/ + // custom:todo FAIL. Reason: AmountZero() function test_compound() public { fundAndApproveAccountV2(address(this), TEST_VALUE); uint256 initialEscrowBalance = rewardEscrowV2.escrowedBalanceOf(address(this)); @@ -38,6 +39,7 @@ contract StakingRewardsV2CompoundTests is DefaultStakingV2Setup { assertEq(rewardEscrowV2.unstakedEscrowedBalanceOf(address(this)), 0); } + // custom:todo FAIL. Reason: AmountZero() function test_compound_Fuzz(uint32 initialStake, uint32 newRewards) public { vm.assume(initialStake > 0); // need reward to be greater than duration so that reward rate is above 0 @@ -96,6 +98,7 @@ contract StakingRewardsV2CompoundTests is DefaultStakingV2Setup { Events //////////////////////////////////////////////////////////////*/ + // custom:todo FAIL. Reason: log != expected log function test_compound_Events() public { fundAndApproveAccountV2(address(this), TEST_VALUE); diff --git a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2OnBehalfActionsTests.t.sol b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2OnBehalfActionsTests.t.sol index a87e1f19c..1c49a34ea 100644 --- a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2OnBehalfActionsTests.t.sol +++ b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2OnBehalfActionsTests.t.sol @@ -204,6 +204,7 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { assertEq(rewardEscrowV2.escrowedBalanceOf(user1), 0); } + // @custom:todo FAIL. Reason: Assertion failed function test_getRewardOnBehalf_Fuzz( uint32 fundingAmount, uint32 newRewards, @@ -355,6 +356,7 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { Get Reward And Stake On Behalf //////////////////////////////////////////////////////////////*/ + // @custom:todo FAIL. Reason: InsufficientUnstakedEscrow(0) function test_Get_Reward_And_Stake_On_Behalf() public { fundAccountAndStakeV2(address(this), TEST_VALUE); @@ -388,6 +390,7 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { assertEq(stakingRewardsV2.escrowedBalanceOf(user1), 0); } + // @custom:todo FAIL. Reason: AmountZero() function test_Get_Reward_And_Stake_On_Behalf_Fuzz( uint32 fundingAmount, uint32 newRewards, @@ -438,6 +441,7 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { Compound On Behalf //////////////////////////////////////////////////////////////*/ + // @custom:todo FAIL. Reason: AmountZero() function test_compoundOnBehalf() public { fundAndApproveAccountV2(address(this), TEST_VALUE); uint256 initialEscrowBalance = rewardEscrowV2.escrowedBalanceOf(address(this)); @@ -469,6 +473,7 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { assertEq(rewardEscrowV2.unstakedEscrowedBalanceOf(address(this)), 0); } + // @custom:todo FAIL. Reason: AmountZero() function test_compoundOnBehalf_Fuzz( uint32 initialStake, uint32 newRewards, @@ -538,6 +543,7 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { stakingRewardsV2.approveOperator(operator, approved); } + // @custom:todo FAIL. Reason: Assertion failed function test_getRewardOnBehalf_Emits_Event() public { fundAccountAndStakeV2(address(this), TEST_VALUE); addNewRewardsToStakingRewardsV2(1 weeks); diff --git a/test/foundry/unit/StakingRewardsV2/StakingV2RewardCalculations.t.sol b/test/foundry/unit/StakingRewardsV2/StakingV2RewardCalculations.t.sol index 430ccd86b..ad42ac508 100644 --- a/test/foundry/unit/StakingRewardsV2/StakingV2RewardCalculations.t.sol +++ b/test/foundry/unit/StakingRewardsV2/StakingV2RewardCalculations.t.sol @@ -66,6 +66,7 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { assertEq(rewards, expectedRewards * numberOfPeriods); } + // @custom:todo FAIL. Reason: assertion failed function test_Staking_Rewards_One_Staker_In_Single_Reward_Period_Fuzz( uint64 _initialStake, uint64 _reward, @@ -119,6 +120,7 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { assertEq(rewards, expectedRewards); } + // @custom:todo FAIL. Reason: assertion failed function test_Staking_Rewards_Multiple_Stakers_In_Single_Reward_Period_Fuzz( uint64 _initialStake, uint64 _reward, @@ -178,6 +180,7 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { assertEq(rewards, expectedRewards); } + // @custom:todo FAIL. Reason: assertion failed function test_Staking_Rewards_One_Staker_Two_Reward_Periods_Fuzz( uint64 _initialStake, uint64 _reward, @@ -234,6 +237,7 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { assertEq(rewards, expectedRewards); } + // @custom:todo FAIL. Reason: assertion failed function test_Staking_Rewards_Three_Rounds_Fuzz( uint64 _initialStake, uint64 _reward, @@ -310,6 +314,7 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { assertEq(rewards, expectedRewards); } + // @custom:todo FAIL. Reason: assertion failed function test_Staking_Rewards_Multiple_Rounds_Fuzz( uint64 _initialStake, uint64 _reward, @@ -370,6 +375,7 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { } } + // @custom:todo FAIL. Reason: assertion failed function test_Staking_Rewards_Multiple_Rounds_And_Stakers_Fuzz( uint64 _initialStake, uint64 _reward, From 371f847492328b185c88ed647ea39444718788f2 Mon Sep 17 00:00:00 2001 From: Flocqst Date: Mon, 8 Jul 2024 18:07:49 +0200 Subject: [PATCH 07/21] =?UTF-8?q?=F0=9F=91=B7=20Adjust=20compound=20reward?= =?UTF-8?q?s=20to=20newly=20non=20escrowed=20rewards?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/StakingRewardsV2.sol | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/contracts/StakingRewardsV2.sol b/contracts/StakingRewardsV2.sol index 506abfb95..a5090d796 100644 --- a/contracts/StakingRewardsV2.sol +++ b/contracts/StakingRewardsV2.sol @@ -214,7 +214,14 @@ contract StakingRewardsV2 is ///////////////////////////////////////////////////////////////*/ /// @inheritdoc IStakingRewardsV2 - function stake(uint256 _amount) external whenNotPaused updateReward(msg.sender) { + function stake(uint256 _amount) external whenNotPaused { + _stake(_amount); + + // transfer token to this contract from the caller + kwenta.transferFrom(msg.sender, address(this), _amount); + } + + function _stake(uint256 _amount) external whenNotPaused updateReward(msg.sender) { if (_amount == 0) revert AmountZero(); // update state @@ -224,9 +231,6 @@ contract StakingRewardsV2 is // emit staking event and index msg.sender emit Staked(msg.sender, _amount); - - // transfer token to this contract from the caller - kwenta.transferFrom(msg.sender, address(this), _amount); } /// @inheritdoc IStakingRewardsV2 @@ -350,6 +354,22 @@ contract StakingRewardsV2 is } } + function _getRewardCompounding(address _account) + internal + whenNotPaused + updateReward(_account) + returns (uint256 reward) + { + uint256 reward = rewards[_account]; + if (reward > 0) { + // update state (first) + rewards[_account] = 0; + + // emit reward claimed event and index account + emit RewardPaid(_account, reward); + } + } + /// @inheritdoc IStakingRewardsV2 function compound() external { _compound(msg.sender); @@ -358,8 +378,8 @@ contract StakingRewardsV2 is /// @dev internal helper to compound for a given account /// @param _account the account to compound for function _compound(address _account) internal { - _getReward(_account); - _stakeEscrow(_account, unstakedEscrowedBalanceOf(_account)); + uint256 reward = _getRewardCompounding(_account); + _stake(reward); } /*/////////////////////////////////////////////////////////////// From b4b69a743524465abbe795c36858623e9ecfcc6b Mon Sep 17 00:00:00 2001 From: Flocqst Date: Tue, 16 Jul 2024 18:36:34 +0200 Subject: [PATCH 08/21] =?UTF-8?q?=F0=9F=91=B7=20adjust=20for=20compounding?= =?UTF-8?q?=20on=20behalf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/StakingRewardsV2.sol | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/contracts/StakingRewardsV2.sol b/contracts/StakingRewardsV2.sol index a5090d796..9089f1677 100644 --- a/contracts/StakingRewardsV2.sol +++ b/contracts/StakingRewardsV2.sol @@ -215,22 +215,26 @@ contract StakingRewardsV2 is /// @inheritdoc IStakingRewardsV2 function stake(uint256 _amount) external whenNotPaused { - _stake(_amount); + _stake(msg.sender, _amount); // transfer token to this contract from the caller kwenta.transferFrom(msg.sender, address(this), _amount); } - function _stake(uint256 _amount) external whenNotPaused updateReward(msg.sender) { + function _stake(address _account, uint256 _amount) + internal + whenNotPaused + updateReward(msg.sender) + { if (_amount == 0) revert AmountZero(); // update state - userLastStakeTime[msg.sender] = block.timestamp; + userLastStakeTime[_account] = block.timestamp; _addTotalSupplyCheckpoint(totalSupply() + _amount); - _addBalancesCheckpoint(msg.sender, balanceOf(msg.sender) + _amount); + _addBalancesCheckpoint(_account, balanceOf(_account) + _amount); - // emit staking event and index msg.sender - emit Staked(msg.sender, _amount); + // emit staking event and index _account + emit Staked(_account, _amount); } /// @inheritdoc IStakingRewardsV2 @@ -360,7 +364,7 @@ contract StakingRewardsV2 is updateReward(_account) returns (uint256 reward) { - uint256 reward = rewards[_account]; + reward = rewards[_account]; if (reward > 0) { // update state (first) rewards[_account] = 0; @@ -379,7 +383,7 @@ contract StakingRewardsV2 is /// @param _account the account to compound for function _compound(address _account) internal { uint256 reward = _getRewardCompounding(_account); - _stake(reward); + _stake(_account, reward); } /*/////////////////////////////////////////////////////////////// From d33ee53de5eedc7fee41d3d73f73cd9b9789a0d1 Mon Sep 17 00:00:00 2001 From: Flocqst Date: Tue, 16 Jul 2024 18:40:57 +0200 Subject: [PATCH 09/21] =?UTF-8?q?=E2=9C=85=20Adjust=20tests=20to=20non=20e?= =?UTF-8?q?scrowed=20rewards?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/stakingV2.migration.t.sol | 31 ++-- .../integration/stakingV2.upgrade.t.sol | 19 +-- .../StakingRewardsV2/StakingRewardsV2.t.sol | 21 +-- .../StakingRewardsV2Compound.t.sol | 38 ++--- ...StakingRewardsV2OnBehalfActionsTests.t.sol | 135 +++-------------- .../StakingV2RewardCalculations.t.sol | 138 +++++++++--------- test/foundry/utils/setup/StakingV2Setup.t.sol | 1 + 7 files changed, 129 insertions(+), 254 deletions(-) diff --git a/test/foundry/integration/stakingV2.migration.t.sol b/test/foundry/integration/stakingV2.migration.t.sol index 322489d87..66b01ebc6 100644 --- a/test/foundry/integration/stakingV2.migration.t.sol +++ b/test/foundry/integration/stakingV2.migration.t.sol @@ -10,7 +10,6 @@ contract StakingV2MigrationTests is StakingTestHelpers { Migration Tests //////////////////////////////////////////////////////////////*/ - // @custom:todo FAIL. Reason: AmountZero() function test_Migrate_Then_Move_Funds_From_V1_To_V2_And_Generate_New_Rewards() public { // Stake tokens in StakingV1 fundAccountAndStakeV1(user1, 10 ether); @@ -99,15 +98,25 @@ contract StakingV2MigrationTests is StakingTestHelpers { warpAndMint(2 weeks); warpAndMint(2 weeks); + // checks everything is staked + assertEq(kwenta.balanceOf(user1), 0); + assertEq(kwenta.balanceOf(user2), 0); + assertEq(kwenta.balanceOf(user3), 0); + // get rewards getStakingRewardsV2(user1); getStakingRewardsV2(user2); getStakingRewardsV2(user3); + // assert v2 rewards have been earned + assertGt(kwenta.balanceOf(user1), 0); + assertGt(kwenta.balanceOf(user2), 0); + assertGt(kwenta.balanceOf(user3), 0); + // stake the rewards - stakeAllUnstakedEscrowV2(user1); - stakeAllUnstakedEscrowV2(user2); - stakeAllUnstakedEscrowV2(user3); + stakeFundsV2(user1, kwenta.balanceOf(user1)); + stakeFundsV2(user2, kwenta.balanceOf(user2)); + stakeFundsV2(user3, kwenta.balanceOf(user3)); // check StakingRewardsV1 balance unchanged assertEq(stakingRewardsV1.nonEscrowedBalanceOf(user1), 0); @@ -136,11 +145,6 @@ contract StakingV2MigrationTests is StakingTestHelpers { user2NonEscrowedStakeV2 = stakingRewardsV2.nonEscrowedBalanceOf(user2); user3NonEscrowedStakeV2 = stakingRewardsV2.nonEscrowedBalanceOf(user3); - // assert v2 rewards have been earned - assertGt(rewardEscrowV2.escrowedBalanceOf(user1), 0); - assertGt(rewardEscrowV2.escrowedBalanceOf(user2), 0); - assertGt(rewardEscrowV2.escrowedBalanceOf(user3), 0); - // v2 staked balance is equal to escrowed + non-escrowed balance assertEq(stakingRewardsV2.balanceOf(user1), user1EscrowStakedV2 + user1NonEscrowedStakeV2); assertEq(stakingRewardsV2.balanceOf(user2), user2EscrowStakedV2 + user2NonEscrowedStakeV2); @@ -152,7 +156,6 @@ contract StakingV2MigrationTests is StakingTestHelpers { assertEq(rewardEscrowV2.escrowedBalanceOf(user3), user3EscrowStakedV2); } - // @custom:todo FAIL. Reason: AmountZero() function test_Migrate_Then_Move_Funds_From_V1_To_V2_And_Generate_New_Rewards_Fuzz( uint32 maxFundingAmount, uint8 numberOfStakers @@ -245,8 +248,11 @@ contract StakingV2MigrationTests is StakingTestHelpers { // get rewards getStakingRewardsV2(stakers[i]); + // assert v2 rewards have been earned + assertGt(kwenta.balanceOf(stakers[i]), 0); + // stake the rewards - stakeAllUnstakedEscrowV2(stakers[i]); + stakeFundsV2(stakers[i], kwenta.balanceOf(stakers[i])); } // check StakingRewardsV1 balance unchanged @@ -266,9 +272,6 @@ contract StakingV2MigrationTests is StakingTestHelpers { uint256 userEscrowStakedV2 = stakingRewardsV2.escrowedBalanceOf(stakers[i]); uint256 userNonEscrowedStakeV2 = stakingRewardsV2.nonEscrowedBalanceOf(stakers[i]); - // assert v2 rewards have been earned - assertGt(rewardEscrowV2.escrowedBalanceOf(stakers[i]), 0); - // v2 staked balance is equal to escrowed + non-escrowed balance assertEq( stakingRewardsV2.balanceOf(stakers[i]), userEscrowStakedV2 + userNonEscrowedStakeV2 diff --git a/test/foundry/integration/stakingV2.upgrade.t.sol b/test/foundry/integration/stakingV2.upgrade.t.sol index 748e08d7b..ebef44b54 100644 --- a/test/foundry/integration/stakingV2.upgrade.t.sol +++ b/test/foundry/integration/stakingV2.upgrade.t.sol @@ -92,7 +92,6 @@ contract StakingV2UpgradeTests is DefaultStakingV2Setup { Upgrade StakingRewardsV2 //////////////////////////////////////////////////////////////*/ - // @custom:todo FAIL. Reason: assertion failed function test_Upgrade_StakingRewardsV2_To_V3() public { address stakingRewardsV3Implementation = deployStakingRewardsV3Implementation(); @@ -106,7 +105,6 @@ contract StakingV2UpgradeTests is DefaultStakingV2Setup { testStakingV2StillWorking(); } - // @custom:todo FAIL. Reason: assertion failed function test_Upgrade_And_Call_StakingRewardsV2_To_V3() public { address stakingRewardsV3Implementation = deployStakingRewardsV3Implementation(); @@ -126,7 +124,6 @@ contract StakingV2UpgradeTests is DefaultStakingV2Setup { Upgrade RewardEscrowV2 //////////////////////////////////////////////////////////////*/ - // @custom:todo FAIL. Reason: assertion failed function test_Upgrade_RewardEscrowV2_To_V3() public { address rewardEscrowV3Implementation = address(new MockRewardEscrowV3(address(kwenta), address(0x1))); @@ -141,7 +138,6 @@ contract StakingV2UpgradeTests is DefaultStakingV2Setup { testStakingV2StillWorking(); } - // @custom:todo FAIL. Reason: assertion failed function test_Upgrade_And_Call_RewardEscrowV2_To_V3() public { address rewardEscrowV3Implementation = address(new MockRewardEscrowV3(address(kwenta), address(0x1))); @@ -162,7 +158,6 @@ contract StakingV2UpgradeTests is DefaultStakingV2Setup { UPGRADE ESCROW MIGRATOR //////////////////////////////////////////////////////////////*/ - // @custom:todo FAIL. Reason: assertion failed function test_Upgrade_EscrowMigrator_To_V2() public { address escrowMigratorV2Impl = deployEscrowMigratorImpl(); @@ -201,9 +196,7 @@ contract StakingV2UpgradeTests is DefaultStakingV2Setup { { stakingRewardsV3Implementation = address( new MockStakingRewardsV3( - address(kwenta), - address(rewardEscrowV2), - address(rewardsNotifier) + address(kwenta), address(rewardEscrowV2), address(rewardsNotifier) ) ); } @@ -239,14 +232,16 @@ contract StakingV2UpgradeTests is DefaultStakingV2Setup { // claim the rewards getStakingRewardsV2(user1); assertEq(1 ether, stakingRewardsV2.balanceOf(user1)); - assertEq(2, rewardEscrowV2.balanceOf(user1)); - assertEq(1 ether + 1 weeks, rewardEscrowV2.escrowedBalanceOf(user1)); - assertEq(1 ether + 1 weeks, rewardEscrowV2.unstakedEscrowedBalanceOf(user1)); + assertEq(1 weeks, kwenta.balanceOf(user1)); + assertEq(1, rewardEscrowV2.balanceOf(user1)); + assertEq(1 ether, rewardEscrowV2.escrowedBalanceOf(user1)); + assertEq(1 ether, rewardEscrowV2.unstakedEscrowedBalanceOf(user1)); // stake the rewards stakeAllUnstakedEscrowV2(user1); + stakeFundsV2(user1, 1 weeks); assertEq(2 ether + 1 weeks, stakingRewardsV2.balanceOf(user1)); - assertEq(1 ether + 1 weeks, stakingRewardsV2.escrowedBalanceOf(user1)); + assertEq(1 ether, stakingRewardsV2.escrowedBalanceOf(user1)); assertEq(0, rewardEscrowV2.unstakedEscrowedBalanceOf(user1)); } diff --git a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2.t.sol b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2.t.sol index 5d22aa9a5..d588226bf 100644 --- a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2.t.sol +++ b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2.t.sol @@ -313,7 +313,6 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { stakingRewardsV2.getRewardOnBehalf(address(this)); } - // @custom:todo FAIL. Reason: AmountZero() function test_Cannot_Compound_When_Paused() public { // fund and stake fundAndApproveAccountV2(address(this), TEST_VALUE); @@ -340,7 +339,6 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { stakingRewardsV2.compound(); } - // @custom:todo FAIL. Reason: AmountZero() function test_Cannot_Compound_On_Behalf_When_Paused() public { // approve operator stakingRewardsV2.approveOperator(user1, true); @@ -383,13 +381,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); @@ -740,15 +732,14 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { getReward //////////////////////////////////////////////////////////////*/ - // @custom:todo FAIL; Reason: assertion failed - function test_getReward_Increases_Balance_In_Escrow() public { + function test_getReward_Increases_Balance() public { fundAndApproveAccountV2(address(this), TEST_VALUE); - uint256 initialEscrowBalance = rewardEscrowV2.escrowedBalanceOf(address(this)); - // stake stakingRewardsV2.stake(TEST_VALUE); + uint256 initialBalance = kwenta.balanceOf(address(this)); + // configure reward rate vm.prank(address(rewardsNotifier)); stakingRewardsV2.notifyRewardAmount(TEST_VALUE); @@ -759,8 +750,8 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { // get reward stakingRewardsV2.getReward(); - // check reward escrow balance increased - assertGt(rewardEscrowV2.escrowedBalanceOf(address(this)), initialEscrowBalance); + // check reward balance increased + assertGt(kwenta.balanceOf(address(this)), initialBalance); } /*////////////////////////////////////////////////////////////// diff --git a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2Compound.t.sol b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2Compound.t.sol index 483140adc..133e29fdb 100644 --- a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2Compound.t.sol +++ b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2Compound.t.sol @@ -11,14 +11,14 @@ contract StakingRewardsV2CompoundTests is DefaultStakingV2Setup { Compound Function //////////////////////////////////////////////////////////////*/ - // custom:todo FAIL. Reason: AmountZero() function test_compound() public { fundAndApproveAccountV2(address(this), TEST_VALUE); - uint256 initialEscrowBalance = rewardEscrowV2.escrowedBalanceOf(address(this)); // stake stakingRewardsV2.stake(TEST_VALUE); + uint256 initialBalance = kwenta.balanceOf(address(this)); + // configure reward rate addNewRewardsToStakingRewardsV2(TEST_VALUE); @@ -28,18 +28,12 @@ contract StakingRewardsV2CompoundTests is DefaultStakingV2Setup { // compound rewards stakingRewardsV2.compound(); - // check reward escrow balance increased - uint256 finalEscrowBalance = rewardEscrowV2.escrowedBalanceOf(address(this)); - assertGt(finalEscrowBalance, initialEscrowBalance); - - // check all escrowed rewards were staked - uint256 totalRewards = finalEscrowBalance - initialEscrowBalance; - assertEq(totalRewards, stakingRewardsV2.escrowedBalanceOf(address(this))); - assertEq(totalRewards + TEST_VALUE, stakingRewardsV2.balanceOf(address(this))); - assertEq(rewardEscrowV2.unstakedEscrowedBalanceOf(address(this)), 0); + // check all rewards were staked and that staker balance increased + uint256 finalBalance = kwenta.balanceOf(address(this)); + assertEq(initialBalance, finalBalance); + assertGt(stakingRewardsV2.balanceOf(address(this)), TEST_VALUE); } - // custom:todo FAIL. Reason: AmountZero() function test_compound_Fuzz(uint32 initialStake, uint32 newRewards) public { vm.assume(initialStake > 0); // need reward to be greater than duration so that reward rate is above 0 @@ -47,11 +41,11 @@ contract StakingRewardsV2CompoundTests is DefaultStakingV2Setup { fundAndApproveAccountV2(address(this), initialStake); - uint256 initialEscrowBalance = rewardEscrowV2.escrowedBalanceOf(address(this)); - // stake stakingRewardsV2.stake(initialStake); + uint256 initialBalance = kwenta.balanceOf(address(this)); + // configure reward rate addNewRewardsToStakingRewardsV2(newRewards); @@ -61,15 +55,10 @@ contract StakingRewardsV2CompoundTests is DefaultStakingV2Setup { // compound rewards stakingRewardsV2.compound(); - // check reward escrow balance increased - uint256 finalEscrowBalance = rewardEscrowV2.escrowedBalanceOf(address(this)); - assertGt(finalEscrowBalance, initialEscrowBalance); - - // check all escrowed rewards were staked - uint256 totalRewards = finalEscrowBalance - initialEscrowBalance; - assertEq(totalRewards, stakingRewardsV2.escrowedBalanceOf(address(this))); - assertEq(totalRewards + initialStake, stakingRewardsV2.balanceOf(address(this))); - assertEq(rewardEscrowV2.unstakedEscrowedBalanceOf(address(this)), 0); + // check all rewards were staked and that staker balance increased + uint256 finalBalance = kwenta.balanceOf(address(this)); + assertEq(initialBalance, finalBalance); + assertGt(stakingRewardsV2.balanceOf(address(this)), initialStake); } /*////////////////////////////////////////////////////////////// @@ -98,7 +87,6 @@ contract StakingRewardsV2CompoundTests is DefaultStakingV2Setup { Events //////////////////////////////////////////////////////////////*/ - // custom:todo FAIL. Reason: log != expected log function test_compound_Events() public { fundAndApproveAccountV2(address(this), TEST_VALUE); @@ -115,7 +103,7 @@ contract StakingRewardsV2CompoundTests is DefaultStakingV2Setup { vm.expectEmit(true, true, false, true); emit RewardPaid(address(this), 1 weeks); vm.expectEmit(true, true, false, true); - emit EscrowStaked(address(this), 1 weeks); + emit Staked(address(this), 1 weeks); // compound rewards stakingRewardsV2.compound(); diff --git a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2OnBehalfActionsTests.t.sol b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2OnBehalfActionsTests.t.sol index 1c49a34ea..5546e1a43 100644 --- a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2OnBehalfActionsTests.t.sol +++ b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2OnBehalfActionsTests.t.sol @@ -200,11 +200,11 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { stakingRewardsV2.getRewardOnBehalf(address(this)); // check rewards - assertEq(rewardEscrowV2.escrowedBalanceOf(address(this)), 1 weeks); + assertEq(kwenta.balanceOf(address(this)), 1 weeks); + assertEq(kwenta.balanceOf(user1), 0); assertEq(rewardEscrowV2.escrowedBalanceOf(user1), 0); } - // @custom:todo FAIL. Reason: Assertion failed function test_getRewardOnBehalf_Fuzz( uint32 fundingAmount, uint32 newRewards, @@ -219,9 +219,9 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { fundAccountAndStakeV2(owner, fundingAmount); - // assert initial rewards are 0 - assertEq(rewardEscrowV2.escrowedBalanceOf(owner), 0); - assertEq(rewardEscrowV2.escrowedBalanceOf(operator), 0); + // assert initial balances are 0 + assertEq(kwenta.balanceOf(owner), 0); + assertEq(kwenta.balanceOf(operator), 0); // send in rewards addNewRewardsToStakingRewardsV2(newRewards); @@ -238,8 +238,8 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { stakingRewardsV2.getRewardOnBehalf(owner); // check rewards - assertGt(rewardEscrowV2.escrowedBalanceOf(owner), 0); - assertEq(rewardEscrowV2.escrowedBalanceOf(operator), 0); + assertGt(kwenta.balanceOf(owner), 0); + assertEq(kwenta.balanceOf(operator), 0); } /*////////////////////////////////////////////////////////////// @@ -352,103 +352,18 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { stakingRewardsV2.stakeEscrowOnBehalf(owner, amountToEscrowStake); } - /*////////////////////////////////////////////////////////////// - Get Reward And Stake On Behalf - //////////////////////////////////////////////////////////////*/ - - // @custom:todo FAIL. Reason: InsufficientUnstakedEscrow(0) - function test_Get_Reward_And_Stake_On_Behalf() public { - fundAccountAndStakeV2(address(this), TEST_VALUE); - - // assert initial rewards are 0 - assertEq(rewardEscrowV2.escrowedBalanceOf(address(this)), 0); - assertEq(rewardEscrowV2.escrowedBalanceOf(user1), 0); - - // send in 604800 (1 week) of rewards - (using 1 week for round numbers) - addNewRewardsToStakingRewardsV2(1 weeks); - - // fast forward 1 week - one complete period - vm.warp(block.timestamp + stakingRewardsV2.rewardsDuration()); - - // approve operator - stakingRewardsV2.approveOperator(user1, true); - - // claim rewards on behalf - vm.prank(user1); - stakingRewardsV2.getRewardOnBehalf(address(this)); - - // check rewards - assertEq(rewardEscrowV2.escrowedBalanceOf(address(this)), 1 weeks); - assertEq(rewardEscrowV2.escrowedBalanceOf(user1), 0); - - // stake escrow on behalf - vm.prank(user1); - stakingRewardsV2.stakeEscrowOnBehalf(address(this), 1 weeks); - - // check final escrowed balances - assertEq(stakingRewardsV2.escrowedBalanceOf(address(this)), 1 weeks); - assertEq(stakingRewardsV2.escrowedBalanceOf(user1), 0); - } - - // @custom:todo FAIL. Reason: AmountZero() - function test_Get_Reward_And_Stake_On_Behalf_Fuzz( - uint32 fundingAmount, - uint32 newRewards, - address owner, - address operator - ) public { - vm.assume(fundingAmount > 0); - vm.assume(newRewards > stakingRewardsV2.rewardsDuration()); - vm.assume(owner != address(0)); - vm.assume(operator != address(0)); - vm.assume(operator != owner); - - fundAccountAndStakeV2(owner, fundingAmount); - - // assert initial rewards are 0 - assertEq(rewardEscrowV2.escrowedBalanceOf(owner), 0); - assertEq(rewardEscrowV2.escrowedBalanceOf(operator), 0); - - // send in rewards - addNewRewardsToStakingRewardsV2(newRewards); - - // fast forward 1 week - one complete period - vm.warp(block.timestamp + stakingRewardsV2.rewardsDuration()); - - // approve operator - vm.prank(owner); - stakingRewardsV2.approveOperator(operator, true); - - // claim rewards on behalf - vm.prank(operator); - stakingRewardsV2.getRewardOnBehalf(owner); - - // check rewards - uint256 rewardEscrowBalance = rewardEscrowV2.escrowedBalanceOf(owner); - assertGt(rewardEscrowBalance, 0); - assertEq(rewardEscrowV2.escrowedBalanceOf(operator), 0); - - // stake escrow on behalf - vm.prank(operator); - stakingRewardsV2.stakeEscrowOnBehalf(owner, rewardEscrowBalance); - - // check final escrowed balances - assertEq(stakingRewardsV2.escrowedBalanceOf(owner), rewardEscrowBalance); - assertEq(stakingRewardsV2.escrowedBalanceOf(operator), 0); - } - /*////////////////////////////////////////////////////////////// Compound On Behalf //////////////////////////////////////////////////////////////*/ - // @custom:todo FAIL. Reason: AmountZero() function test_compoundOnBehalf() public { fundAndApproveAccountV2(address(this), TEST_VALUE); - uint256 initialEscrowBalance = rewardEscrowV2.escrowedBalanceOf(address(this)); // stake stakingRewardsV2.stake(TEST_VALUE); + uint256 initialBalance = kwenta.balanceOf(address(this)); + // configure reward rate addNewRewardsToStakingRewardsV2(TEST_VALUE); @@ -462,18 +377,12 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { vm.prank(user1); stakingRewardsV2.compoundOnBehalf(address(this)); - // check reward escrow balance increased - uint256 finalEscrowBalance = rewardEscrowV2.escrowedBalanceOf(address(this)); - assertGt(finalEscrowBalance, initialEscrowBalance); - - // check all escrowed rewards were staked - uint256 totalRewards = finalEscrowBalance - initialEscrowBalance; - assertEq(totalRewards, stakingRewardsV2.escrowedBalanceOf(address(this))); - assertEq(totalRewards + TEST_VALUE, stakingRewardsV2.balanceOf(address(this))); - assertEq(rewardEscrowV2.unstakedEscrowedBalanceOf(address(this)), 0); + // check all rewards were staked and that staker balance increased + uint256 finalBalance = kwenta.balanceOf(address(this)); + assertEq(initialBalance, finalBalance); + assertGt(stakingRewardsV2.balanceOf(address(this)), TEST_VALUE); } - // @custom:todo FAIL. Reason: AmountZero() function test_compoundOnBehalf_Fuzz( uint32 initialStake, uint32 newRewards, @@ -489,12 +398,12 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { fundAndApproveAccountV2(owner, initialStake); - uint256 initialEscrowBalance = rewardEscrowV2.escrowedBalanceOf(owner); - // stake vm.prank(owner); stakingRewardsV2.stake(initialStake); + uint256 initialBalance = kwenta.balanceOf(owner); + // configure reward rate addNewRewardsToStakingRewardsV2(newRewards); @@ -509,15 +418,10 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { vm.prank(operator); stakingRewardsV2.compoundOnBehalf(owner); - // check reward escrow balance increased - uint256 finalEscrowBalance = rewardEscrowV2.escrowedBalanceOf(owner); - assertGt(finalEscrowBalance, initialEscrowBalance); - - // check all escrowed rewards were staked - uint256 totalRewards = finalEscrowBalance - initialEscrowBalance; - assertEq(totalRewards, stakingRewardsV2.escrowedBalanceOf(owner)); - assertEq(totalRewards + initialStake, stakingRewardsV2.balanceOf(owner)); - assertEq(rewardEscrowV2.unstakedEscrowedBalanceOf(owner), 0); + // check all rewards were staked and that staker balance increased + uint256 finalBalance = kwenta.balanceOf(owner); + assertEq(initialBalance, finalBalance); + assertGt(stakingRewardsV2.balanceOf(owner), initialStake); } /*////////////////////////////////////////////////////////////// @@ -543,7 +447,6 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { stakingRewardsV2.approveOperator(operator, approved); } - // @custom:todo FAIL. Reason: Assertion failed function test_getRewardOnBehalf_Emits_Event() public { fundAccountAndStakeV2(address(this), TEST_VALUE); addNewRewardsToStakingRewardsV2(1 weeks); diff --git a/test/foundry/unit/StakingRewardsV2/StakingV2RewardCalculations.t.sol b/test/foundry/unit/StakingRewardsV2/StakingV2RewardCalculations.t.sol index ad42ac508..9570d0220 100644 --- a/test/foundry/unit/StakingRewardsV2/StakingV2RewardCalculations.t.sol +++ b/test/foundry/unit/StakingRewardsV2/StakingV2RewardCalculations.t.sol @@ -19,10 +19,10 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { // user1 earns 100% of rewards fundAccountAndStakeV2(user1, initialStake); - // get initial rewards - uint256 rewards = rewardEscrowV2.escrowedBalanceOf(user1); - // assert initial rewards are 0 - assertEq(rewards, 0); + // get initial balance + uint256 balance = kwenta.balanceOf(user1); + // assert initial balance is 0 (everything is staked and rewards are 0) + assertEq(balance, 0); // send in 604800 (1 week) of rewards - (using 1 week for round numbers) addNewRewardsToStakingRewardsV2(1 weeks); @@ -47,8 +47,8 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { uint256 expectedRewards = 1 weeks; // check rewards - rewards = rewardEscrowV2.escrowedBalanceOf(user1); - assertEq(rewards, expectedRewards); + balance = kwenta.balanceOf(user1); + assertEq(balance, expectedRewards); // send in another 604800 (1 week) of rewards addNewRewardsToStakingRewardsV2(1 weeks); @@ -60,13 +60,12 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { getStakingRewardsV2(user1); // check rewards - rewards = rewardEscrowV2.escrowedBalanceOf(user1); + balance = kwenta.balanceOf(user1); // we exect the same amount of rewards again as this week was exactly the same as the previous one uint256 numberOfPeriods = 2; - assertEq(rewards, expectedRewards * numberOfPeriods); + assertEq(balance, expectedRewards * numberOfPeriods); } - // @custom:todo FAIL. Reason: assertion failed function test_Staking_Rewards_One_Staker_In_Single_Reward_Period_Fuzz( uint64 _initialStake, uint64 _reward, @@ -84,10 +83,10 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { // user1 earns 100% of rewards fundAccountAndStakeV2(user1, initialStake); - // get initial rewards - uint256 rewards = rewardEscrowV2.escrowedBalanceOf(user1); - // assert initial rewards are 0 - assertEq(rewards, 0); + // get initial balance + uint256 balance = kwenta.balanceOf(user1); + // assert initial balance is 0 (everything is staked and rewards are 0) + assertEq(balance, 0); // calculate expected reward uint256 expectedRewards = getExpectedRewardV2(reward, waitTime, user1); @@ -102,8 +101,8 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { getStakingRewardsV2(user1); // check rewards - rewards = rewardEscrowV2.escrowedBalanceOf(user1); - assertEq(rewards, expectedRewards); + balance = kwenta.balanceOf(user1); + assertEq(balance, expectedRewards); // move forward to the end of the rewards period jumpToEndOfRewardsPeriod(waitTime); @@ -116,11 +115,10 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { getStakingRewardsV2(user1); // check rewards - rewards = rewardEscrowV2.escrowedBalanceOf(user1); - assertEq(rewards, expectedRewards); + balance = kwenta.balanceOf(user1); + assertEq(balance, expectedRewards); } - // @custom:todo FAIL. Reason: assertion failed function test_Staking_Rewards_Multiple_Stakers_In_Single_Reward_Period_Fuzz( uint64 _initialStake, uint64 _reward, @@ -144,10 +142,10 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { // user1 earns 100% of rewards fundAccountAndStakeV2(user1, initialStake); - // get initial rewards - uint256 rewards = rewardEscrowV2.escrowedBalanceOf(user1); - // assert initial rewards are 0 - assertEq(rewards, 0); + // get initial balance + uint256 balance = kwenta.balanceOf(user1); + // assert initial balance is 0 (everything is staked and rewards are 0) + assertEq(balance, 0); // calculate expected reward uint256 expectedRewards = getExpectedRewardV2(reward, waitTime, user1); @@ -162,8 +160,8 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { getStakingRewardsV2(user1); // check rewards - rewards = rewardEscrowV2.escrowedBalanceOf(user1); - assertEq(rewards, expectedRewards); + balance = kwenta.balanceOf(user1); + assertEq(balance, expectedRewards); // move forward to the end of the rewards period jumpToEndOfRewardsPeriod(waitTime); @@ -176,11 +174,10 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { getStakingRewardsV2(user1); // check rewards - rewards = rewardEscrowV2.escrowedBalanceOf(user1); - assertEq(rewards, expectedRewards); + balance = kwenta.balanceOf(user1); + assertEq(balance, expectedRewards); } - // @custom:todo FAIL. Reason: assertion failed function test_Staking_Rewards_One_Staker_Two_Reward_Periods_Fuzz( uint64 _initialStake, uint64 _reward, @@ -195,10 +192,10 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { // user1 earns 100% of rewards fundAccountAndStakeV2(user1, initialStake); - // get initial rewards - uint256 rewards = rewardEscrowV2.escrowedBalanceOf(user1); - // assert initial rewards are 0 - assertEq(rewards, 0); + // get initial balance + uint256 balance = kwenta.balanceOf(user1); + // assert initial balance is 0 (everything is staked and rewards are 0) + assertEq(balance, 0); // calculate expected reward uint256 expectedRewards = getExpectedRewardV2(reward, waitTime, user1); @@ -213,8 +210,8 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { getStakingRewardsV2(user1); // check rewards - rewards = rewardEscrowV2.escrowedBalanceOf(user1); - assertEq(rewards, expectedRewards); + balance = kwenta.balanceOf(user1); + assertEq(balance, expectedRewards); // move forward to the end of the rewards period jumpToEndOfRewardsPeriod(waitTime); @@ -233,11 +230,10 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { getStakingRewardsV2(user1); // check rewards - rewards = rewardEscrowV2.escrowedBalanceOf(user1); - assertEq(rewards, expectedRewards); + balance = kwenta.balanceOf(user1); + assertEq(balance, expectedRewards); } - // @custom:todo FAIL. Reason: assertion failed function test_Staking_Rewards_Three_Rounds_Fuzz( uint64 _initialStake, uint64 _reward, @@ -252,10 +248,10 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { // user1 earns 100% of rewards fundAccountAndStakeV2(user1, initialStake); - // get initial rewards - uint256 rewards = rewardEscrowV2.escrowedBalanceOf(user1); - // assert initial rewards are 0 - assertEq(rewards, 0); + // get initial balance + uint256 balance = kwenta.balanceOf(user1); + // assert initial balance is 0 (everything is staked and rewards are 0) + assertEq(balance, 0); // calculate expected reward uint256 expectedRewards = getExpectedRewardV2(reward, waitTime, user1); @@ -270,8 +266,8 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { getStakingRewardsV2(user1); // check rewards - rewards = rewardEscrowV2.escrowedBalanceOf(user1); - assertEq(rewards, expectedRewards); + balance = kwenta.balanceOf(user1); + assertEq(balance, expectedRewards); // move forward to the end of the rewards period jumpToEndOfRewardsPeriod(waitTime); @@ -290,8 +286,8 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { getStakingRewardsV2(user1); // check rewards - rewards = rewardEscrowV2.escrowedBalanceOf(user1); - assertEq(rewards, expectedRewards); + balance = kwenta.balanceOf(user1); + assertEq(balance, expectedRewards); // move forward to the end of the rewards period jumpToEndOfRewardsPeriod(waitTime); @@ -310,11 +306,10 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { getStakingRewardsV2(user1); // check rewards - rewards = rewardEscrowV2.escrowedBalanceOf(user1); - assertEq(rewards, expectedRewards); + balance = kwenta.balanceOf(user1); + assertEq(balance, expectedRewards); } - // @custom:todo FAIL. Reason: assertion failed function test_Staking_Rewards_Multiple_Rounds_Fuzz( uint64 _initialStake, uint64 _reward, @@ -331,10 +326,10 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { // user1 earns 100% of rewards fundAccountAndStakeV2(user1, initialStake); - // get initial rewards - uint256 rewards = rewardEscrowV2.escrowedBalanceOf(user1); - // assert initial rewards are 0 - assertEq(rewards, 0); + // get initial balance + uint256 balance = kwenta.balanceOf(user1); + // assert initial balance is 0 (everything is staked and rewards are 0) + assertEq(balance, 0); // calculate expected reward uint256 expectedRewards = getExpectedRewardV2(reward, waitTime, user1); @@ -349,8 +344,8 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { getStakingRewardsV2(user1); // check rewards - rewards = rewardEscrowV2.escrowedBalanceOf(user1); - assertEq(rewards, expectedRewards); + balance = kwenta.balanceOf(user1); + assertEq(balance, expectedRewards); for (uint256 i = 0; i < numberOfRounds; i++) { // move forward to the end of the rewards period @@ -370,12 +365,11 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { getStakingRewardsV2(user1); // check rewards - rewards = rewardEscrowV2.escrowedBalanceOf(user1); - assertEq(rewards, expectedRewards); + balance = kwenta.balanceOf(user1); + assertEq(balance, expectedRewards); } } - // @custom:todo FAIL. Reason: assertion failed function test_Staking_Rewards_Multiple_Rounds_And_Stakers_Fuzz( uint64 _initialStake, uint64 _reward, @@ -400,10 +394,10 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { // user1 earns 100% of rewards fundAccountAndStakeV2(user1, initialStake); - // get initial rewards - uint256 rewards = rewardEscrowV2.escrowedBalanceOf(user1); - // assert initial rewards are 0 - assertEq(rewards, 0); + // get initial balance + uint256 balance = kwenta.balanceOf(user1); + // assert initial balance is 0 (everything is staked and rewards are 0) + assertEq(balance, 0); // calculate expected reward uint256 expectedRewards = getExpectedRewardV2(reward, waitTime, user1); @@ -418,8 +412,8 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { getStakingRewardsV2(user1); // check rewards - rewards = rewardEscrowV2.escrowedBalanceOf(user1); - assertEq(rewards, expectedRewards); + balance = kwenta.balanceOf(user1); + assertEq(balance, expectedRewards); for (uint256 i = 0; i < numberOfRounds; i++) { // add another staker @@ -443,8 +437,8 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { getStakingRewardsV2(user1); // check rewards - rewards = rewardEscrowV2.escrowedBalanceOf(user1); - assertEq(rewards, expectedRewards); + balance = kwenta.balanceOf(user1); + assertEq(balance, expectedRewards); } } @@ -457,10 +451,10 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { // user1 earns 100% of rewards fundAccountAndStakeV2(user1, initialStake); - // get initial rewards - uint256 rewards = rewardEscrowV2.escrowedBalanceOf(user1); - // assert initial rewards are 0 - assertEq(rewards, 0); + // get initial balance + uint256 balance = kwenta.balanceOf(user1); + // assert initial balance is 0 (everything is staked and rewards are 0) + assertEq(balance, 0); // send in 604800 (1 week) of rewards - (using 1 week for round numbers) addNewRewardsToStakingRewardsV2(1 weeks); @@ -485,8 +479,8 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { uint256 expectedRewards = 1 weeks / 2; // check rewards - rewards = rewardEscrowV2.escrowedBalanceOf(user1); - assertEq(rewards, expectedRewards); + balance = kwenta.balanceOf(user1); + assertEq(balance, expectedRewards); // fast forward 0.5 weeks - to the end of this period vm.warp(block.timestamp + lengthOfPeriod / 2); @@ -495,8 +489,8 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { getStakingRewardsV2(user1); // check rewards - rewards = rewardEscrowV2.escrowedBalanceOf(user1); + balance = kwenta.balanceOf(user1); // we exect to claim the other half of this weeks rewards - assertEq(rewards, expectedRewards * 2); + assertEq(balance, expectedRewards * 2); } } diff --git a/test/foundry/utils/setup/StakingV2Setup.t.sol b/test/foundry/utils/setup/StakingV2Setup.t.sol index 509e9296c..9e8c210e4 100644 --- a/test/foundry/utils/setup/StakingV2Setup.t.sol +++ b/test/foundry/utils/setup/StakingV2Setup.t.sol @@ -21,6 +21,7 @@ contract StakingV2Setup is StakingV1Setup { event Transfer(address indexed from, address indexed to, uint256 indexed tokenId); event OperatorApproved(address owner, address operator, bool approved); event RewardPaid(address indexed account, uint256 reward); + event Staked(address indexed user, uint256 amount); event EscrowStaked(address indexed user, uint256 amount); event Vested(address indexed beneficiary, uint256 value); event VestingEntryCreated( From 9dee0eb57e3510e78b5ca15c7deceb8c66463467 Mon Sep 17 00:00:00 2001 From: Flocqst Date: Tue, 16 Jul 2024 18:50:30 +0200 Subject: [PATCH 10/21] =?UTF-8?q?=E2=9C=85=20fix=20fork=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../integration/stakingV2.migration.fork.t.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/foundry/integration/stakingV2.migration.fork.t.sol b/test/foundry/integration/stakingV2.migration.fork.t.sol index 9f46fe327..ecfa090a4 100644 --- a/test/foundry/integration/stakingV2.migration.fork.t.sol +++ b/test/foundry/integration/stakingV2.migration.fork.t.sol @@ -33,9 +33,8 @@ contract StakingV2MigrationForkTests is StakingTestHelpers { // set owners address code to trick the test into allowing onlyOwner functions to be called via script vm.etch(owner, address(new Migrate()).code); - (rewardEscrowV2, stakingRewardsV2, escrowMigrator, rewardsNotifier) = Migrate( - owner - ).runCompleteMigrationProcess({ + (rewardEscrowV2, stakingRewardsV2, escrowMigrator, rewardsNotifier) = Migrate(owner) + .runCompleteMigrationProcess({ _owner: owner, _kwenta: address(kwenta), _supplySchedule: address(supplySchedule), @@ -101,8 +100,11 @@ contract StakingV2MigrationForkTests is StakingTestHelpers { // get rewards getStakingRewardsV2(user1); + // assert v2 rewards have been earned + assertGt(kwenta.balanceOf(user1), 0); + // stake the rewards - stakeAllUnstakedEscrowV2(user1); + stakeFundsV2(user1, kwenta.balanceOf(user1)); // check StakingRewardsV1 balance unchanged assertEq(stakingRewardsV1.nonEscrowedBalanceOf(user1), 0); @@ -115,8 +117,6 @@ contract StakingV2MigrationForkTests is StakingTestHelpers { uint256 user1EscrowStakedV2 = stakingRewardsV2.escrowedBalanceOf(user1); uint256 user1NonEscrowedStakeV2 = stakingRewardsV2.nonEscrowedBalanceOf(user1); - // assert v2 rewards have been earned - assertGt(rewardEscrowV2.escrowedBalanceOf(user1), 0); // v2 staked balance is equal to escrowed + non-escrowed balance assertEq(stakingRewardsV2.balanceOf(user1), user1EscrowStakedV2 + user1NonEscrowedStakeV2); // v2 reward escrow balance is equal to escrow staked balance From fb03e8167eed97d70752bb2cc0e3fc136f88eae0 Mon Sep 17 00:00:00 2001 From: Flocqst Date: Wed, 17 Jul 2024 15:44:53 +0200 Subject: [PATCH 11/21] =?UTF-8?q?=E2=9C=85=20Add=20unstakeEscrowAdmin=20te?= =?UTF-8?q?sts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../StakingRewardsV2/StakingRewardsV2.t.sol | 70 +++++++++++++++++++ .../utils/helpers/StakingTestHelpers.t.sol | 5 ++ 2 files changed, 75 insertions(+) diff --git a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2.t.sol b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2.t.sol index 2f280d7aa..67e9c9db0 100644 --- a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2.t.sol +++ b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2.t.sol @@ -65,6 +65,13 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { stakingRewardsV2.recoverERC20(address(kwenta), 0); } + function test_Only_RewardEscrow_Can_Call_unstakeEscrowAdmin() public { + stakeEscrowedFundsV2(address(this), TEST_VALUE); + vm.warp(block.timestamp + 2 weeks); + vm.expectRevert(IStakingRewardsV2.OnlyRewardEscrow.selector); + stakingRewardsV2.unstakeEscrowAdmin(address(this), TEST_VALUE); + } + function test_Cannot_unstakeEscrow_Invalid_Amount() public { vm.expectRevert(abi.encodeWithSelector(IStakingRewardsV2.InsufficientBalance.selector, 0)); unstakeEscrowedFundsV2(address(this), TEST_VALUE); @@ -1009,6 +1016,24 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { assertEq(initialEscrowTokenBalance, finalEscrowTokenBalance); } + function test_unstakeEscrowAdmin_Does_Not_Change_Token_Balances() public { + // stake escrow + stakeEscrowedFundsV2(address(this), 1 weeks); + + uint256 initialTokenBalance = kwenta.balanceOf(address(this)); + uint256 initialEscrowTokenBalance = kwenta.balanceOf(address(rewardEscrowV2)); + + // unstake escrow admin + unstakeEscrowedFundsAdminV2(address(this), 1 weeks); + + uint256 finalTokenBalance = kwenta.balanceOf(address(this)); + uint256 finalEscrowTokenBalance = kwenta.balanceOf(address(rewardEscrowV2)); + + // check both values unchanged + assertEq(initialTokenBalance, finalTokenBalance); + assertEq(initialEscrowTokenBalance, finalEscrowTokenBalance); + } + function test_unstakeEscrow_Does_Change_totalSupply() public { // stake escrow stakeEscrowedFundsV2(address(this), 1 weeks); @@ -1024,6 +1049,21 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { assertEq(initialTotalSupply - 1 weeks, finalTotalSupply); } + function test_unstakeEscrowAdmin_Does_Change_totalSupply() public { + // stake escrow + stakeEscrowedFundsV2(address(this), 1 weeks); + + uint256 initialTotalSupply = stakingRewardsV2.totalSupply(); + + // unstake escrow admin + unstakeEscrowedFundsAdminV2(address(this), 1 weeks); + + uint256 finalTotalSupply = stakingRewardsV2.totalSupply(); + + // check total supply decreased + assertEq(initialTotalSupply - 1 weeks, finalTotalSupply); + } + function test_unstakeEscrow_Does_Change_Balances_Mapping() public { // stake escrow stakeEscrowedFundsV2(address(this), 1 weeks); @@ -1039,6 +1079,21 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { assertEq(initialBalance - 1 weeks, finalBalance); } + function test_unstakeEscrowAdmin_Does_Change_Balances_Mapping() public { + // stake escrow + stakeEscrowedFundsV2(address(this), 1 weeks); + + uint256 initialBalance = stakingRewardsV2.balanceOf(address(this)); + + // unstake escrow admin + unstakeEscrowedFundsAdminV2(address(this), 1 weeks); + + uint256 finalBalance = stakingRewardsV2.balanceOf(address(this)); + + // check balance decreased + assertEq(initialBalance - 1 weeks, finalBalance); + } + function test_unstakeEscrow_Does_Change_Escrowed_Balances_Mapping() public { // stake escrow stakeEscrowedFundsV2(address(this), 1 weeks); @@ -1053,6 +1108,21 @@ contract StakingRewardsV2Test is DefaultStakingV2Setup { // check balance decreased assertEq(initialEscrowBalance - 1 weeks, finalEscrowBalance); } + + function test_unstakeEscrowAdmin_Does_Change_Escrowed_Balances_Mapping() public { + // stake escrow + stakeEscrowedFundsV2(address(this), 1 weeks); + + uint256 initialEscrowBalance = stakingRewardsV2.escrowedBalanceOf(address(this)); + + // unstake escrow admin + unstakeEscrowedFundsAdminV2(address(this), 1 weeks); + + uint256 finalEscrowBalance = stakingRewardsV2.escrowedBalanceOf(address(this)); + + // check balance decreased + assertEq(initialEscrowBalance - 1 weeks, finalEscrowBalance); + } function test_Cannot_unstakeEscrow_More_Than_Escrow_Staked() public { // stake escrow diff --git a/test/foundry/utils/helpers/StakingTestHelpers.t.sol b/test/foundry/utils/helpers/StakingTestHelpers.t.sol index f2c052884..32751e4ed 100644 --- a/test/foundry/utils/helpers/StakingTestHelpers.t.sol +++ b/test/foundry/utils/helpers/StakingTestHelpers.t.sol @@ -200,6 +200,11 @@ contract StakingTestHelpers is StakingV2Setup { stakingRewardsV2.stakeEscrow(_amount); } + function unstakeEscrowedFundsAdminV2(address _account, uint256 _amount) internal { + vm.prank(address(rewardEscrowV2)); + stakingRewardsV2.unstakeEscrowAdmin(_account, _amount); + } + function unstakeEscrowedFundsV2(address _account, uint256 _amount) internal { vm.prank(_account); stakingRewardsV2.unstakeEscrow(_amount); From 6336698e7b6f5b1f2ccb86bf89c5064025f1fcba Mon Sep 17 00:00:00 2001 From: Flocqst Date: Wed, 17 Jul 2024 15:45:59 +0200 Subject: [PATCH 12/21] =?UTF-8?q?=E2=9C=85=20rename=20function=20helper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../StakingV2Checkpointing.t.sol | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/foundry/unit/StakingRewardsV2/StakingV2Checkpointing.t.sol b/test/foundry/unit/StakingRewardsV2/StakingV2Checkpointing.t.sol index c9c88a318..e4a06f683 100644 --- a/test/foundry/unit/StakingRewardsV2/StakingV2Checkpointing.t.sol +++ b/test/foundry/unit/StakingRewardsV2/StakingV2Checkpointing.t.sol @@ -538,14 +538,14 @@ contract StakingV2CheckpointingTests is DefaultStakingV2Setup { } function test_balanceAtTime_With_Unstake_Before_Block() public { - uint256 timestampToFind = xCooldownPeriods(4); + uint256 timestampToFind = xPeriods(4); uint256 expectedValue; uint256 totalStaked; for (uint256 i = 0; i < 10; i++) { uint256 amount = TEST_VALUE; totalStaked += amount; - if (block.timestamp == xCooldownPeriods(2)) { + if (block.timestamp == xPeriods(2)) { stakingRewardsV2.unstake(amount); totalStaked -= amount; } @@ -562,14 +562,14 @@ contract StakingV2CheckpointingTests is DefaultStakingV2Setup { } function test_balanceAtTime_With_Unstake_After_Block() public { - uint256 timestampToFind = xCooldownPeriods(4); + uint256 timestampToFind = xPeriods(4); uint256 expectedValue; uint256 totalStaked; for (uint256 i = 0; i < 10; i++) { uint256 amount = TEST_VALUE; totalStaked += amount; - if (block.timestamp == xCooldownPeriods(5)) { + if (block.timestamp == xPeriods(5)) { stakingRewardsV2.unstake(amount); totalStaked -= amount; } @@ -586,7 +586,7 @@ contract StakingV2CheckpointingTests is DefaultStakingV2Setup { } function test_balanceAtTime_With_Unstake_Before_And_After_Block() public { - uint256 timestampToFind = xCooldownPeriods(4); + uint256 timestampToFind = xPeriods(4); uint256 expectedValue; uint256 totalStaked; @@ -594,14 +594,14 @@ contract StakingV2CheckpointingTests is DefaultStakingV2Setup { uint256 amount = TEST_VALUE; totalStaked += amount; fundAccountAndStakeV2(address(this), amount); - if (block.timestamp == xCooldownPeriods(2)) { + if (block.timestamp == xPeriods(2)) { vm.warp(block.timestamp + 1); stakingRewardsV2.unstake(amount); stakingRewardsV2.unstake(amount); totalStaked -= amount; totalStaked -= amount; } - if (block.timestamp == xCooldownPeriods(5)) { + if (block.timestamp == xPeriods(5)) { vm.warp(block.timestamp + 1); stakingRewardsV2.unstake(amount); stakingRewardsV2.unstake(amount); @@ -930,7 +930,7 @@ contract StakingV2CheckpointingTests is DefaultStakingV2Setup { Helpers //////////////////////////////////////////////////////////////*/ - function xCooldownPeriods(uint256 numCooldowns) public pure returns (uint256) { + function xPeriods(uint256 numCooldowns) public pure returns (uint256) { return 1 + (numCooldowns * 1); } } From 7ff1a0a2234b3a5b7102925bccab86212b6e6bd6 Mon Sep 17 00:00:00 2001 From: Flocqst Date: Wed, 17 Jul 2024 15:57:55 +0200 Subject: [PATCH 13/21] =?UTF-8?q?=F0=9F=93=9A=20Add=20author=20handle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/EscrowMigrator.sol | 1 + contracts/RewardEscrowV2.sol | 2 +- contracts/StakingRewardsV2.sol | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/EscrowMigrator.sol b/contracts/EscrowMigrator.sol index 518d1e5b4..613a00e2b 100644 --- a/contracts/EscrowMigrator.sol +++ b/contracts/EscrowMigrator.sol @@ -33,6 +33,7 @@ import {IStakingRewardsIntegrator} from "./interfaces/IStakingRewardsIntegrator. /// @title KWENTA Escrow Migrator /// Used to migrate escrow entries from RewardEscrowV1 to RewardEscrowV2 /// @author tommyrharper (tom@zkconsulting.xyz) +/// @author Flocqst (florian@kwenta.io) contract EscrowMigrator is IEscrowMigrator, Ownable2StepUpgradeable, diff --git a/contracts/RewardEscrowV2.sol b/contracts/RewardEscrowV2.sol index 747507751..8387ca166 100644 --- a/contracts/RewardEscrowV2.sol +++ b/contracts/RewardEscrowV2.sol @@ -19,7 +19,7 @@ import {IEscrowMigrator} from "./interfaces/IEscrowMigrator.sol"; /// @title KWENTA Reward Escrow V2 /// @author Originally inspired by SYNTHETIX RewardEscrow /// @author Kwenta's RewardEscrow V1 by JaredBorders (jaredborders@proton.me), JChiaramonte7 (jeremy@bytecode.llc) -/// @author RewardEscrowV2 by tommyrharper (tom@zkconsulting.xyz) +/// @author RewardEscrowV2 by tommyrharper (tom@zkconsulting.xyz), Flocqst (florian@kwenta.io) /// @notice Updated version of Synthetix's RewardEscrow with new features specific to Kwenta contract RewardEscrowV2 is IRewardEscrowV2, diff --git a/contracts/StakingRewardsV2.sol b/contracts/StakingRewardsV2.sol index d0007e992..21f908a2a 100644 --- a/contracts/StakingRewardsV2.sol +++ b/contracts/StakingRewardsV2.sol @@ -15,7 +15,7 @@ import {IRewardEscrowV2} from "./interfaces/IRewardEscrowV2.sol"; /// @title KWENTA Staking Rewards V2 /// @author Originally inspired by SYNTHETIX StakingRewards /// @author Kwenta's StakingRewards V1 by JaredBorders (jaredborders@proton.me), JChiaramonte7 (jeremy@bytecode.llc) -/// @author StakingRewardsV2 (this) by tommyrharper (tom@zkconsulting.xyz) +/// @author StakingRewardsV2 (this) by tommyrharper (tom@zkconsulting.xyz), Flocqst (florian@kwenta.io) /// @notice Updated version of Synthetix's StakingRewards with new features specific to Kwenta contract StakingRewardsV2 is IStakingRewardsV2, From 15eea33c46cc51cb09b4995b2a48a9a473902742 Mon Sep 17 00:00:00 2001 From: Flocqst Date: Mon, 22 Jul 2024 16:13:02 +0200 Subject: [PATCH 14/21] =?UTF-8?q?=F0=9F=91=B7=20fix=20updateReward=20modif?= =?UTF-8?q?ier=20for=20compoundOnBehalf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/StakingRewardsV2.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/contracts/StakingRewardsV2.sol b/contracts/StakingRewardsV2.sol index 9089f1677..6a011f9f6 100644 --- a/contracts/StakingRewardsV2.sol +++ b/contracts/StakingRewardsV2.sol @@ -214,7 +214,7 @@ contract StakingRewardsV2 is ///////////////////////////////////////////////////////////////*/ /// @inheritdoc IStakingRewardsV2 - function stake(uint256 _amount) external whenNotPaused { + function stake(uint256 _amount) external { _stake(msg.sender, _amount); // transfer token to this contract from the caller @@ -224,7 +224,7 @@ contract StakingRewardsV2 is function _stake(address _account, uint256 _amount) internal whenNotPaused - updateReward(msg.sender) + updateReward(_account) { if (_amount == 0) revert AmountZero(); @@ -358,6 +358,8 @@ contract StakingRewardsV2 is } } + /// @notice Get the reward of the given account for compounding. + /// @dev Retrieves the reward without transferring it, as it will be staked immediately after. function _getRewardCompounding(address _account) internal whenNotPaused From 827e85ca3d19b6285fcd8597fb9cc67509291e60 Mon Sep 17 00:00:00 2001 From: Flocqst Date: Tue, 27 Aug 2024 14:51:09 +0200 Subject: [PATCH 15/21] =?UTF-8?q?=E2=9C=85=20Adjust=20stake=20Escrow=20on?= =?UTF-8?q?=20behalf=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...StakingRewardsV2OnBehalfActionsTests.t.sol | 124 ++++-------------- 1 file changed, 23 insertions(+), 101 deletions(-) diff --git a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2OnBehalfActionsTests.t.sol b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2OnBehalfActionsTests.t.sol index d9d9ca21e..b333769cb 100644 --- a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2OnBehalfActionsTests.t.sol +++ b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2OnBehalfActionsTests.t.sol @@ -175,77 +175,6 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { stakingRewardsV2.approveOperator(owner, true); } - /*////////////////////////////////////////////////////////////// - Get Reward On Behalf - //////////////////////////////////////////////////////////////*/ - - function test_getRewardOnBehalf() public { - fundAccountAndStakeV2(address(this), TEST_VALUE); - - // assert initial rewards are 0 - assertEq(rewardEscrowV2.escrowedBalanceOf(address(this)), 0); - assertEq(rewardEscrowV2.escrowedBalanceOf(user1), 0); - - // send in 604800 (1 week) of rewards - (using 1 week for round numbers) - addNewRewardsToStakingRewardsV2(1 weeks, 1 weeks); - - // fast forward 1 week - one complete period - vm.warp(block.timestamp + stakingRewardsV2.rewardsDuration()); - - // approve operator - stakingRewardsV2.approveOperator(user1, true); - - // claim rewards on behalf - vm.prank(user1); - stakingRewardsV2.getRewardOnBehalf(address(this)); - - // check rewards - assertEq(kwenta.balanceOf(address(this)), 1 weeks); - assertEq(kwenta.balanceOf(user1), 0); - assertEq(rewardEscrowV2.escrowedBalanceOf(user1), 0); - assertEq(usdc.balanceOf(address(this)), 1 weeks); - assertEq(usdc.balanceOf(user1), 0); - } - - function test_getRewardOnBehalf_Fuzz( - uint32 fundingAmount, - uint32 newRewards, - address owner, - address operator - ) public { - vm.assume(fundingAmount > 0); - vm.assume(newRewards > stakingRewardsV2.rewardsDuration()); - vm.assume(owner != address(0)); - vm.assume(operator != address(0)); - vm.assume(operator != owner); - - fundAccountAndStakeV2(owner, fundingAmount); - - // assert initial balances are 0 - assertEq(kwenta.balanceOf(owner), 0); - assertEq(kwenta.balanceOf(operator), 0); - - // send in rewards - addNewRewardsToStakingRewardsV2(newRewards, newRewards); - - // fast forward 1 week - one complete period - vm.warp(block.timestamp + stakingRewardsV2.rewardsDuration()); - - // approve operator - vm.prank(owner); - stakingRewardsV2.approveOperator(operator, true); - - // claim rewards on behalf - vm.prank(operator); - stakingRewardsV2.getRewardOnBehalf(owner); - - // check rewards - assertGt(rewardEscrowV2.escrowedBalanceOf(owner), 0); - assertEq(rewardEscrowV2.escrowedBalanceOf(operator), 0); - assertGt(usdc.balanceOf(owner), 0); - assertEq(usdc.balanceOf(operator), 0); - } - /*////////////////////////////////////////////////////////////// Stake Escrow On Behalf //////////////////////////////////////////////////////////////*/ @@ -357,18 +286,20 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { } /*////////////////////////////////////////////////////////////// - Get Reward And Stake On Behalf + Get Reward On Behalf //////////////////////////////////////////////////////////////*/ - function test_Get_Reward_And_Stake_On_Behalf() public { + function test_Get_Reward_On_Behalf() public { fundAccountAndStakeV2(address(this), TEST_VALUE); // assert initial rewards are 0 - assertEq(rewardEscrowV2.escrowedBalanceOf(address(this)), 0); - assertEq(rewardEscrowV2.escrowedBalanceOf(user1), 0); + assertEq(kwenta.balanceOf(address(this)), 0); + assertEq(kwenta.balanceOf(user1), 0); + assertEq(usdc.balanceOf(address(this)), 0); + assertEq(usdc.balanceOf(user1), 0); // send in 604800 (1 week) of rewards - (using 1 week for round numbers) - addNewRewardsToStakingRewardsV2(1 weeks, 0); + addNewRewardsToStakingRewardsV2(1 weeks, 1 weeks); // fast forward 1 week - one complete period vm.warp(block.timestamp + stakingRewardsV2.rewardsDuration()); @@ -381,26 +312,22 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { stakingRewardsV2.getRewardOnBehalf(address(this)); // check rewards - assertEq(rewardEscrowV2.escrowedBalanceOf(address(this)), 1 weeks); - assertEq(rewardEscrowV2.escrowedBalanceOf(user1), 0); - - // stake escrow on behalf - vm.prank(user1); - stakingRewardsV2.stakeEscrowOnBehalf(address(this), 1 weeks); - - // check final escrowed balances - assertEq(stakingRewardsV2.escrowedBalanceOf(address(this)), 1 weeks); - assertEq(stakingRewardsV2.escrowedBalanceOf(user1), 0); + assertEq(kwenta.balanceOf(address(this)), 1 weeks); + assertEq(kwenta.balanceOf(user1), 0); + assertEq(usdc.balanceOf(address(this)), 1 weeks); + assertEq(usdc.balanceOf(user1), 0); } - function test_Get_Reward_And_Stake_On_Behalf_Fuzz( + function test_Get_Reward_On_Behalf_Fuzz( uint32 fundingAmount, uint32 newRewards, + uint32 newUsdcRewards, address owner, address operator ) public { vm.assume(fundingAmount > 0); vm.assume(newRewards > stakingRewardsV2.rewardsDuration()); + vm.assume(newUsdcRewards > stakingRewardsV2.rewardsDuration()); vm.assume(owner != address(0)); vm.assume(operator != address(0)); vm.assume(operator != owner); @@ -408,11 +335,13 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { fundAccountAndStakeV2(owner, fundingAmount); // assert initial rewards are 0 - assertEq(rewardEscrowV2.escrowedBalanceOf(owner), 0); - assertEq(rewardEscrowV2.escrowedBalanceOf(operator), 0); + assertEq(kwenta.balanceOf(owner), 0); + assertEq(kwenta.balanceOf(operator), 0); + assertEq(usdc.balanceOf(owner), 0); + assertEq(usdc.balanceOf(operator), 0); // send in rewards - addNewRewardsToStakingRewardsV2(newRewards, 0); + addNewRewardsToStakingRewardsV2(newRewards, newUsdcRewards); // fast forward 1 week - one complete period vm.warp(block.timestamp + stakingRewardsV2.rewardsDuration()); @@ -426,17 +355,10 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { stakingRewardsV2.getRewardOnBehalf(owner); // check rewards - uint256 rewardEscrowBalance = rewardEscrowV2.escrowedBalanceOf(owner); - assertGt(rewardEscrowBalance, 0); - assertEq(rewardEscrowV2.escrowedBalanceOf(operator), 0); - - // stake escrow on behalf - vm.prank(operator); - stakingRewardsV2.stakeEscrowOnBehalf(owner, rewardEscrowBalance); - - // check final escrowed balances - assertEq(stakingRewardsV2.escrowedBalanceOf(owner), rewardEscrowBalance); - assertEq(stakingRewardsV2.escrowedBalanceOf(operator), 0); + assertGt(kwenta.balanceOf(owner), 0); + assertEq(kwenta.balanceOf(operator), 0); + assertGt(usdc.balanceOf(owner), 0); + assertEq(usdc.balanceOf(operator), 0); } /*////////////////////////////////////////////////////////////// From 48a2b16469adfb4794c75f5b7820a2d40dd5c29e Mon Sep 17 00:00:00 2001 From: Flocqst Date: Tue, 27 Aug 2024 15:55:34 +0200 Subject: [PATCH 16/21] =?UTF-8?q?=E2=9C=85=20Adjust=20precision=20delta=20?= =?UTF-8?q?usdc=20reward=20calculation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../unit/StakingRewardsV2/StakingV2RewardCalculations.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/foundry/unit/StakingRewardsV2/StakingV2RewardCalculations.t.sol b/test/foundry/unit/StakingRewardsV2/StakingV2RewardCalculations.t.sol index a4e831388..de0979fbe 100644 --- a/test/foundry/unit/StakingRewardsV2/StakingV2RewardCalculations.t.sol +++ b/test/foundry/unit/StakingRewardsV2/StakingV2RewardCalculations.t.sol @@ -516,7 +516,7 @@ contract StakingV2RewardCalculationTests is DefaultStakingV2Setup { balance = kwenta.balanceOf(user1); assertEq(balance, expectedRewards); rewardsUsdc = usdc.balanceOf(user1); - assertApproxEqAbs(rewardsUsdc, expectedUsdcRewards, 10); + assertApproxEqAbs(rewardsUsdc, expectedUsdcRewards, 50); } } From 0d173e791982e313f15d8501fe0cc736c429fbd2 Mon Sep 17 00:00:00 2001 From: Flocqst Date: Wed, 11 Sep 2024 14:55:35 +0200 Subject: [PATCH 17/21] =?UTF-8?q?=F0=9F=91=B7=20Remove=20uses=20of=20userL?= =?UTF-8?q?astStakeTime?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/StakingRewardsV2.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/StakingRewardsV2.sol b/contracts/StakingRewardsV2.sol index 6b009044f..9fdee6f4b 100644 --- a/contracts/StakingRewardsV2.sol +++ b/contracts/StakingRewardsV2.sol @@ -233,7 +233,6 @@ contract StakingRewardsV2 is if (_amount == 0) revert AmountZero(); // update state - userLastStakeTime[_account] = block.timestamp; _addTotalSupplyCheckpoint(totalSupply() + _amount); _addBalancesCheckpoint(_account, balanceOf(_account) + _amount); @@ -277,7 +276,6 @@ contract StakingRewardsV2 is if (_amount > unstakedEscrow) revert InsufficientUnstakedEscrow(unstakedEscrow); // update state - userLastStakeTime[_account] = block.timestamp; _addBalancesCheckpoint(_account, balanceOf(_account) + _amount); _addEscrowedBalancesCheckpoint(_account, escrowedBalanceOf(_account) + _amount); From 0f1db851c8358c5137dcb76127d673cbd80cd63a Mon Sep 17 00:00:00 2001 From: Flocqst Date: Wed, 11 Sep 2024 15:06:03 +0200 Subject: [PATCH 18/21] =?UTF-8?q?=F0=9F=91=B7=20Refactor=20processReward?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/StakingRewardsV2.sol | 75 ++++++++++++---------------------- 1 file changed, 25 insertions(+), 50 deletions(-) diff --git a/contracts/StakingRewardsV2.sol b/contracts/StakingRewardsV2.sol index 9fdee6f4b..4326a93f2 100644 --- a/contracts/StakingRewardsV2.sol +++ b/contracts/StakingRewardsV2.sol @@ -241,11 +241,7 @@ contract StakingRewardsV2 is } /// @inheritdoc IStakingRewardsV2 - function unstake(uint256 _amount) - public - whenNotPaused - updateReward(msg.sender) - { + function unstake(uint256 _amount) public whenNotPaused updateReward(msg.sender) { if (_amount == 0) revert AmountZero(); uint256 nonEscrowedBalance = nonEscrowedBalanceOf(msg.sender); if (_amount > nonEscrowedBalance) revert InsufficientBalance(nonEscrowedBalance); @@ -293,10 +289,7 @@ contract StakingRewardsV2 is } /// @inheritdoc IStakingRewardsV2 - function unstakeEscrowAdmin(address _account, uint256 _amount) - external - onlyRewardEscrow - { + function unstakeEscrowAdmin(address _account, uint256 _amount) external onlyRewardEscrow { _unstakeEscrow(_account, _amount); } @@ -345,61 +338,43 @@ contract StakingRewardsV2 is whenNotPaused updateReward(_account) { - uint256 reward = rewards[_account]; - if (reward > 0) { - // update state (first) - rewards[_account] = 0; - - // emit reward claimed event and index account - emit RewardPaid(_account, reward); - - // transfer token from this contract to the account - // as newly issued rewards from inflation are now issued as non-escrowed - kwenta.transfer(_to, reward); - } - - uint256 rewardUSDC = rewardsUSDC[_account] / PRECISION; - 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); - } + _processReward(_account, _to, true); } - /// @notice Get the reward of the given account for compounding. - /// @dev Retrieves the Kwenta reward without transferring it, as it will be staked immediately after. - function _getRewardCompounding(address _account) + /// @notice Process KWENTA and USDC rewards + /// @dev transferKwenta is set to false when compounding KWENTA rewards + /// @param _account The address of the account to process rewards for + /// @param _to The address to transfer rewards to + /// @param transferKwenta Boolean flag to determine if Kwenta should be transferred + /// @return kwentaReward The amount of Kwenta reward processed + function _processReward(address _account, address _to, bool transferKwenta) internal - whenNotPaused - updateReward(_account) - returns (uint256 reward) + returns (uint256 kwentaReward) { - reward = rewards[_account]; - if (reward > 0) { + // Process Kwenta reward + kwentaReward = rewards[_account]; + if (kwentaReward > 0) { // update state (first) rewards[_account] = 0; // emit reward claimed event and index account - emit RewardPaid(_account, reward); + emit RewardPaid(_account, kwentaReward); + + if (transferKwenta) { + kwenta.transfer(_to, kwentaReward); + } } - uint256 rewardUSDC = rewardsUSDC[_account] / PRECISION; - if (rewardUSDC > 0) { + // Process USDC reward + uint256 usdcReward = rewardsUSDC[_account] / PRECISION; + if (usdcReward > 0) { // update state (first) rewardsUSDC[_account] = 0; // emit reward claimed event and index account - emit RewardPaidUSDC(_account, rewardUSDC); + emit RewardPaidUSDC(_account, usdcReward); - // transfer token from this contract to the account - // as newly issued rewards from inflation are now issued as non-escrowed - usdc.transfer(_account, rewardUSDC); + usdc.transfer(_to, usdcReward); } } @@ -411,7 +386,7 @@ contract StakingRewardsV2 is /// @dev internal helper to compound for a given account /// @param _account the account to compound for function _compound(address _account) internal { - uint256 reward = _getRewardCompounding(_account); + uint256 reward = _processReward(_account, _account, false); _stake(_account, reward); } From c9e48f6f1b52c20fd1c5752d5a6fa84e9348d4e7 Mon Sep 17 00:00:00 2001 From: Andrew Chiaramonte Date: Mon, 28 Oct 2024 15:41:58 -0400 Subject: [PATCH 19/21] =?UTF-8?q?=F0=9F=91=B7=20add=20updateReward(=5Facco?= =?UTF-8?q?unt)=20modifier=20to=20=5FprocessReward()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/StakingRewardsV2.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/StakingRewardsV2.sol b/contracts/StakingRewardsV2.sol index 4326a93f2..14c54832b 100644 --- a/contracts/StakingRewardsV2.sol +++ b/contracts/StakingRewardsV2.sol @@ -349,6 +349,7 @@ contract StakingRewardsV2 is /// @return kwentaReward The amount of Kwenta reward processed function _processReward(address _account, address _to, bool transferKwenta) internal + updateReward(_account) returns (uint256 kwentaReward) { // Process Kwenta reward From bf210e986e82bb878418db711f5f1db02c79f114 Mon Sep 17 00:00:00 2001 From: Andrew Chiaramonte Date: Mon, 28 Oct 2024 16:51:36 -0400 Subject: [PATCH 20/21] =?UTF-8?q?=E2=9C=85=20test=5Fcompound=20add=20asser?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../unit/StakingRewardsV2/StakingRewardsV2Compound.t.sol | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2Compound.t.sol b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2Compound.t.sol index 41f64ceac..a8cb3a9bc 100644 --- a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2Compound.t.sol +++ b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2Compound.t.sol @@ -18,6 +18,7 @@ contract StakingRewardsV2CompoundTests is DefaultStakingV2Setup { stakingRewardsV2.stake(TEST_VALUE); uint256 initialBalance = kwenta.balanceOf(address(this)); + uint256 initialStakedBalance = stakingRewardsV2.balanceOf(address(this)); // configure reward rate addNewRewardsToStakingRewardsV2(TEST_VALUE, 0); @@ -30,8 +31,10 @@ contract StakingRewardsV2CompoundTests is DefaultStakingV2Setup { // check all rewards were staked and that staker balance increased uint256 finalBalance = kwenta.balanceOf(address(this)); + uint256 finalStakedBalance = stakingRewardsV2.balanceOf(address(this)); assertEq(initialBalance, finalBalance); - assertGt(stakingRewardsV2.balanceOf(address(this)), TEST_VALUE); + assertGt(finalStakedBalance, initialStakedBalance); + assertGt(finalStakedBalance, TEST_VALUE); } function test_compound_Fuzz(uint32 initialStake, uint32 newRewards) public { @@ -45,6 +48,7 @@ contract StakingRewardsV2CompoundTests is DefaultStakingV2Setup { stakingRewardsV2.stake(initialStake); uint256 initialBalance = kwenta.balanceOf(address(this)); + uint256 initialStakedBalance = stakingRewardsV2.balanceOf(address(this)); // configure reward rate addNewRewardsToStakingRewardsV2(newRewards, 0); @@ -57,7 +61,9 @@ contract StakingRewardsV2CompoundTests is DefaultStakingV2Setup { // check all rewards were staked and that staker balance increased uint256 finalBalance = kwenta.balanceOf(address(this)); + uint256 finalStakedBalance = stakingRewardsV2.balanceOf(address(this)); assertEq(initialBalance, finalBalance); + assertGt(finalStakedBalance, initialStakedBalance); assertGt(stakingRewardsV2.balanceOf(address(this)), initialStake); } From c6d26b69e9454d620b1adf8242f6bb98b22a9d58 Mon Sep 17 00:00:00 2001 From: Andrew Chiaramonte Date: Mon, 28 Oct 2024 17:21:25 -0400 Subject: [PATCH 21/21] =?UTF-8?q?=E2=9C=85=20add=20asserts=20to=20test=5Fc?= =?UTF-8?q?ompoundOnBehalf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../StakingRewardsV2OnBehalfActionsTests.t.sol | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2OnBehalfActionsTests.t.sol b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2OnBehalfActionsTests.t.sol index 664746f61..c93a19614 100644 --- a/test/foundry/unit/StakingRewardsV2/StakingRewardsV2OnBehalfActionsTests.t.sol +++ b/test/foundry/unit/StakingRewardsV2/StakingRewardsV2OnBehalfActionsTests.t.sol @@ -372,6 +372,7 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { stakingRewardsV2.stake(TEST_VALUE); uint256 initialBalance = kwenta.balanceOf(address(this)); + uint256 initialStakedBalance = stakingRewardsV2.balanceOf(address(this)); // configure reward rate addNewRewardsToStakingRewardsV2(TEST_VALUE, 0); @@ -388,7 +389,9 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { // check all rewards were staked and that staker balance increased uint256 finalBalance = kwenta.balanceOf(address(this)); + uint256 finalStakedBalance = stakingRewardsV2.balanceOf(address(this)); assertEq(initialBalance, finalBalance); + assertGt(finalStakedBalance, initialStakedBalance); assertGt(stakingRewardsV2.balanceOf(address(this)), TEST_VALUE); } @@ -412,6 +415,7 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { stakingRewardsV2.stake(initialStake); uint256 initialBalance = kwenta.balanceOf(owner); + uint256 initialStakedBalance = stakingRewardsV2.balanceOf(owner); // configure reward rate addNewRewardsToStakingRewardsV2(newRewards, 0); @@ -429,7 +433,9 @@ contract StakingRewardsV2OnBehalfActionsTests is DefaultStakingV2Setup { // check all rewards were staked and that staker balance increased uint256 finalBalance = kwenta.balanceOf(owner); + uint256 finalStakedBalance = stakingRewardsV2.balanceOf(owner); assertEq(initialBalance, finalBalance); + assertGt(finalStakedBalance, initialStakedBalance); assertGt(stakingRewardsV2.balanceOf(owner), initialStake); }