From f34eabb4423980581a89f09b3665a767b6c8ff2b Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Sun, 30 Jun 2024 12:49:04 -0300 Subject: [PATCH 01/13] simplify rewards contract --- src/RewardsDistributor.sol | 133 ++++++++----------------- src/Staking.sol | 2 +- src/interfaces/IRewardsDistributor.sol | 18 +--- test/Staking.t.sol | 12 +-- 4 files changed, 50 insertions(+), 115 deletions(-) diff --git a/src/RewardsDistributor.sol b/src/RewardsDistributor.sol index 45d2016..fb62c22 100644 --- a/src/RewardsDistributor.sol +++ b/src/RewardsDistributor.sol @@ -1,24 +1,26 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.26; -import {console} from "@forge-std/console.sol"; -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol"; import {Ownable2StepUpgradeable} from "@openzeppelin-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; +import {IRewardsDistributor} from "./interfaces/IRewardsDistributor.sol"; -interface IRewardsDistributor { - function distributeRewards() external; -} - -// TODO should be pausable? -contract RewardsDistributor is Ownable2StepUpgradeable { +contract RewardsDistributor is Ownable2StepUpgradeable, IRewardsDistributor { /*////////////////////////////////////////////////////////////// LIBRARIES //////////////////////////////////////////////////////////////*/ using SafeERC20 for IERC20; + /*////////////////////////////////////////////////////////////// + VARIABLES + //////////////////////////////////////////////////////////////*/ + + /// @notice the reward token, i.e. SHU + /// @dev set in initialize, can't be changed + IERC20 public rewardToken; + /*////////////////////////////////////////////////////////////// STRUCTS //////////////////////////////////////////////////////////////*/ @@ -33,125 +35,78 @@ contract RewardsDistributor is Ownable2StepUpgradeable { MAPPINGS/ARRAYS //////////////////////////////////////////////////////////////*/ - mapping(address receiver => mapping(address token => RewardConfiguration configuration)) + mapping(address receiver => RewardConfiguration configuration) public rewardConfigurations; - mapping(address receiver => address[] rewardsTokens) public rewardTokens; - /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ event RewardConfigurationSet( address indexed receiver, - address indexed token, uint256 emissionRate ); - event RewardDistributed( - address indexed receiver, - address indexed token, - uint256 reward - ); - - /// @notice Ensure logic contract is unusable - constructor() { - _disableInitializers(); - } + event RewardCollected(address indexed receiver, uint256 reward); /// @notice Initialize the contract /// @param newOwner The owner of the contract, i.e. the DAO contract address - function initialize(address newOwner) public initializer { - __Ownable2Step_init(); - + constructor(address newOwner, address _rewardToken) { // Transfer ownership to the DAO contract _transferOwnership(newOwner); - } - - /// @notice Add a reward configuration - /// @param receiver The receiver of the rewards - /// @param token The reward token - /// @param emissionRate The emission rate - function setRewardConfiguration( - address receiver, - address token, - uint256 emissionRate - ) external onlyOwner { - require(receiver != address(0), "Invalid receiver"); - require(token != address(0), "No native rewards allowed"); - - if (rewardConfigurations[receiver][token].emissionRate == 0) { - rewardTokens[receiver].push(token); - } - - rewardConfigurations[receiver][token] = RewardConfiguration( - emissionRate, - block.timestamp - ); - - if (emissionRate == 0) { - // remove the token - address[] storage tokens = rewardTokens[receiver]; - for (uint256 i = 0; i < tokens.length; i++) { - if (tokens[i] == token) { - tokens[i] = tokens[tokens.length - 1]; - tokens.pop(); - break; - } - } - } - emit RewardConfigurationSet(receiver, token, emissionRate); + // Set the reward token + rewardToken = IERC20(_rewardToken); } /// @notice Distribute rewards to receiver - /// @param token The reward token - function distributeReward(address token) external { - distributeRewardInternal(msg.sender, token); - } - - /// @notice Distribute rewards to all tokens - function distributeRewards() external { + function collectRewards() external override returns (uint256 rewards) { address receiver = msg.sender; - for (uint256 i = 0; i < rewardTokens[receiver].length; i++) { - distributeRewardInternal(receiver, rewardTokens[receiver][i]); - } - } - - /// @notice Distribute rewards to token - /// @param receiver The receiver of the rewards - /// @param token The reward token - function distributeRewardInternal( - address receiver, - address token - ) internal { RewardConfiguration storage rewardConfiguration = rewardConfigurations[ receiver - ][token]; + ]; // difference in time since last update uint256 timeDelta = block.timestamp - rewardConfiguration.lastUpdate; - if (timeDelta == 0) { + if (rewardConfiguration.emissionRate == 0 || timeDelta == 0) { // nothing to do - return; + return 0; } - uint256 reward = rewardConfiguration.emissionRate * timeDelta; + rewards = rewardConfiguration.emissionRate * timeDelta; // update the last update timestamp rewardConfiguration.lastUpdate = block.timestamp; // transfer the reward - IERC20(token).safeTransfer(receiver, reward); + rewardToken.safeTransfer(receiver, rewards); + + emit RewardCollected(receiver, rewards); + } + + /// @notice Add a reward configuration + /// @param receiver The receiver of the rewards + /// @param emissionRate The emission rate + function setRewardConfiguration( + address receiver, + uint256 emissionRate + ) external override onlyOwner { + require(receiver != address(0), "Invalid receiver"); + + rewardConfigurations[receiver] = RewardConfiguration( + emissionRate, + block.timestamp + ); - emit RewardDistributed(receiver, token, reward); + emit RewardConfigurationSet(receiver, emissionRate); } - function getRewardTokens( - address receiver - ) external view returns (address[] memory) { - return rewardTokens[receiver]; + function withdrawFunds( + address to, + uint256 amount + ) external override onlyOwner { + rewardToken.safeTransfer(to, amount); } } diff --git a/src/Staking.sol b/src/Staking.sol index 575de7a..af9bb8f 100644 --- a/src/Staking.sol +++ b/src/Staking.sol @@ -153,7 +153,7 @@ contract Staking is ERC20VotesUpgradeable, Ownable2StepUpgradeable { /// @notice Update rewards for a keyper modifier updateRewards() { // Distribute rewards - rewardsDistributor.distributeReward(address(stakingToken)); + rewardsDistributor.collectRewards(); _; } diff --git a/src/interfaces/IRewardsDistributor.sol b/src/interfaces/IRewardsDistributor.sol index 7d9a8ef..b616ecd 100644 --- a/src/interfaces/IRewardsDistributor.sol +++ b/src/interfaces/IRewardsDistributor.sol @@ -2,22 +2,12 @@ pragma solidity 0.8.26; interface IRewardsDistributor { + function collectRewards() external returns (uint256); + + function withdrawFunds(address to, uint256 amount) external; + function setRewardConfiguration( address receiver, - address token, uint256 emissionRate ) external; - - function distributeReward(address token) external; - - function distributeRewards() external; - - function getRewardTokens( - address receiver - ) external view returns (address[] memory); - - function rewardConfigurations( - address receiver, - address token - ) external view returns (uint256 emissionRate, uint256 lastUpdate); } diff --git a/test/Staking.t.sol b/test/Staking.t.sol index 862ad0b..80f20de 100644 --- a/test/Staking.t.sol +++ b/test/Staking.t.sol @@ -33,16 +33,7 @@ contract StakingTest is Test { // deploy rewards distributor rewardsDistributor = IRewardsDistributor( - address( - new TransparentUpgradeableProxy( - address(new RewardsDistributor()), - address(this), - abi.encodeWithSignature( - "initialize(address)", - address(this) - ) - ) - ) + new RewardsDistributor(address(this), address(govToken)) ); // deploy staking @@ -67,7 +58,6 @@ contract StakingTest is Test { rewardsDistributor.setRewardConfiguration( address(staking), - address(govToken), REWARD_RATE ); From 5750a7744b563ce220786b5d27e7dd3cc5d0c20f Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Sun, 30 Jun 2024 13:26:19 -0300 Subject: [PATCH 02/13] setRewardConfiguration --- src/RewardsDistributor.sol | 29 ++++++- src/Staking.sol | 2 +- src/interfaces/IRewardsDistributor.sol | 2 + test/RewardsDistributor.t.sol | 104 +++++++++++++++++++++++++ test/Staking.t.sol | 2 + 5 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 test/RewardsDistributor.t.sol diff --git a/src/RewardsDistributor.sol b/src/RewardsDistributor.sol index fb62c22..948bf45 100644 --- a/src/RewardsDistributor.sol +++ b/src/RewardsDistributor.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.26; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {SafeTransferLib} from "@solmate/utils/SafeTransferLib.sol"; import {Ownable2StepUpgradeable} from "@openzeppelin-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; import {IRewardsDistributor} from "./interfaces/IRewardsDistributor.sol"; @@ -49,6 +48,15 @@ contract RewardsDistributor is Ownable2StepUpgradeable, IRewardsDistributor { event RewardCollected(address indexed receiver, uint256 reward); + event RewardTokenSet(address indexed rewardToken); + + /*////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + + /// @notice Thrown when address is zero + error ZeroAddress(); + /// @notice Initialize the contract /// @param newOwner The owner of the contract, i.e. the DAO contract address constructor(address newOwner, address _rewardToken) { @@ -93,7 +101,7 @@ contract RewardsDistributor is Ownable2StepUpgradeable, IRewardsDistributor { address receiver, uint256 emissionRate ) external override onlyOwner { - require(receiver != address(0), "Invalid receiver"); + require(receiver != address(0), ZeroAddress()); rewardConfigurations[receiver] = RewardConfiguration( emissionRate, @@ -103,10 +111,25 @@ contract RewardsDistributor is Ownable2StepUpgradeable, IRewardsDistributor { emit RewardConfigurationSet(receiver, emissionRate); } + /// @notice Withdraw funds from the contract + /// @param to The address to withdraw to + /// @param amount The amount to withdraw function withdrawFunds( address to, uint256 amount - ) external override onlyOwner { + ) public override onlyOwner { rewardToken.safeTransfer(to, amount); } + + /// @notice Set the reward token + /// @param _rewardToken The reward token + function setRewardToken(address _rewardToken) external onlyOwner { + // withdraw remaining old reward token + withdrawFunds(msg.sender, rewardToken.balanceOf(address(this))); + + // set the new reward token + rewardToken = IERC20(_rewardToken); + + emit RewardTokenSet(_rewardToken); + } } diff --git a/src/Staking.sol b/src/Staking.sol index af9bb8f..cc7ee4a 100644 --- a/src/Staking.sol +++ b/src/Staking.sol @@ -119,7 +119,7 @@ contract Staking is ERC20VotesUpgradeable, Ownable2StepUpgradeable { /// amount is less than the minimum stake set by the DAO error FirstStakeLessThanMinStake(); - /// @notice Trownn when amount is zero + /// @notice Trown when amount is zero error ZeroAmount(); /// @notice Thrown when someone try to unstake a stake that doesn't belong diff --git a/src/interfaces/IRewardsDistributor.sol b/src/interfaces/IRewardsDistributor.sol index b616ecd..60c05f9 100644 --- a/src/interfaces/IRewardsDistributor.sol +++ b/src/interfaces/IRewardsDistributor.sol @@ -10,4 +10,6 @@ interface IRewardsDistributor { address receiver, uint256 emissionRate ) external; + + function setRewardToken(address _rewardToken) external; } diff --git a/test/RewardsDistributor.t.sol b/test/RewardsDistributor.t.sol new file mode 100644 index 0000000..632c204 --- /dev/null +++ b/test/RewardsDistributor.t.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import "@forge-std/Test.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +import {RewardsDistributor} from "src/RewardsDistributor.sol"; +import {MockGovToken} from "test/mocks/MockGovToken.sol"; + +contract RewardsDistributorTest is Test { + RewardsDistributor public rewardsDistributor; + MockGovToken public govToken; + + function setUp() public { + // Set the block timestamp to an arbitrary value to avoid introducing assumptions into tests + // based on a starting timestamp of 0, which is the default. + _jumpAhead(1234); + + govToken = new MockGovToken(); + govToken.mint(address(this), 1_000_000_000e18); + vm.label(address(govToken), "govToken"); + + // deploy rewards distributor + rewardsDistributor = new RewardsDistributor( + address(this), + address(govToken) + ); + } + + function _jumpAhead(uint256 _seconds) public { + vm.warp(vm.getBlockTimestamp() + _seconds); + } + + function _setRewardConfiguration( + address receiver, + uint256 emissionRate + ) internal { + emissionRate = bound(emissionRate, 0, 1e18); + rewardsDistributor.setRewardConfiguration(receiver, emissionRate); + } +} + +contract OwnableFunctions is RewardsDistributorTest { + function test_SetRewardConfigurationEmitEvent( + address _receiver, + uint256 _emissionRate + ) public { + vm.expectEmit(); + emit RewardsDistributor.RewardConfigurationSet( + _receiver, + _emissionRate + ); + rewardsDistributor.setRewardConfiguration(_receiver, _emissionRate); + } + + function test_SetRewardConfigurationSetEmissionRate( + address _receiver, + uint256 _emissionRate + ) public { + rewardsDistributor.setRewardConfiguration(_receiver, _emissionRate); + + (uint256 emissionRate, ) = rewardsDistributor.rewardConfigurations( + _receiver + ); + + assertEq(emissionRate, _emissionRate); + } + + function test_SetRewardConfigurationSetLastUpdate( + address _receiver, + uint256 _emissionRate + ) public { + rewardsDistributor.setRewardConfiguration(_receiver, _emissionRate); + + (, uint256 lastUpdate) = rewardsDistributor.rewardConfigurations( + _receiver + ); + assertEq(lastUpdate, vm.getBlockTimestamp()); + } + + function test_RevertIf_SetRewardConfigurationZeroAddress( + uint256 _emissionRate + ) public { + vm.expectRevert(RewardsDistributor.ZeroAddress.selector); + rewardsDistributor.setRewardConfiguration(address(0), _emissionRate); + } + + function test_RevertIf_SetRewardConfigurationNotOwner( + address _anyone, + address _receiver, + uint256 _emissionRate + ) public { + vm.assume(_anyone != address(this)); + + vm.expectRevert( + abi.encodeWithSelector( + Ownable.OwnableUnauthorizedAccount.selector, + _anyone + ) + ); + vm.prank(_anyone); + rewardsDistributor.setRewardConfiguration(_receiver, _emissionRate); + } +} diff --git a/test/Staking.t.sol b/test/Staking.t.sol index 80f20de..bec71f1 100644 --- a/test/Staking.t.sol +++ b/test/Staking.t.sol @@ -1394,6 +1394,8 @@ contract OwnableFunctions is StakingTest { uint256[] memory keyperStakeIds = staking.getKeyperStakeIds(_keyper); assertEq(keyperStakeIds.length, 0, "Wrong stake ids"); } + + // TEST CASES FOR NON OWNERS } contract ViewFunctions is StakingTest { From 0618811de1f45a6f57a24a04ea7117df4855fd5d Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Sun, 30 Jun 2024 13:37:59 -0300 Subject: [PATCH 03/13] setRewardToken / withdrawFunds --- src/RewardsDistributor.sol | 2 + test/RewardsDistributor.t.sol | 91 +++++++++++++++++++++++++++++++++-- test/Staking.t.sol | 4 ++ 3 files changed, 92 insertions(+), 5 deletions(-) diff --git a/src/RewardsDistributor.sol b/src/RewardsDistributor.sol index 948bf45..61e31b3 100644 --- a/src/RewardsDistributor.sol +++ b/src/RewardsDistributor.sol @@ -124,6 +124,8 @@ contract RewardsDistributor is Ownable2StepUpgradeable, IRewardsDistributor { /// @notice Set the reward token /// @param _rewardToken The reward token function setRewardToken(address _rewardToken) external onlyOwner { + require(_rewardToken != address(0), ZeroAddress()); + // withdraw remaining old reward token withdrawFunds(msg.sender, rewardToken.balanceOf(address(this))); diff --git a/test/RewardsDistributor.t.sol b/test/RewardsDistributor.t.sol index 632c204..8da1fad 100644 --- a/test/RewardsDistributor.t.sol +++ b/test/RewardsDistributor.t.sol @@ -41,7 +41,7 @@ contract RewardsDistributorTest is Test { } contract OwnableFunctions is RewardsDistributorTest { - function test_SetRewardConfigurationEmitEvent( + function testFuzz_SetRewardConfigurationEmitEvent( address _receiver, uint256 _emissionRate ) public { @@ -53,7 +53,7 @@ contract OwnableFunctions is RewardsDistributorTest { rewardsDistributor.setRewardConfiguration(_receiver, _emissionRate); } - function test_SetRewardConfigurationSetEmissionRate( + function testFuzz_SetRewardConfigurationSetEmissionRate( address _receiver, uint256 _emissionRate ) public { @@ -66,7 +66,7 @@ contract OwnableFunctions is RewardsDistributorTest { assertEq(emissionRate, _emissionRate); } - function test_SetRewardConfigurationSetLastUpdate( + function testFuzz_SetRewardConfigurationSetLastUpdate( address _receiver, uint256 _emissionRate ) public { @@ -78,14 +78,14 @@ contract OwnableFunctions is RewardsDistributorTest { assertEq(lastUpdate, vm.getBlockTimestamp()); } - function test_RevertIf_SetRewardConfigurationZeroAddress( + function testFuzz_RevertIf_SetRewardConfigurationZeroAddress( uint256 _emissionRate ) public { vm.expectRevert(RewardsDistributor.ZeroAddress.selector); rewardsDistributor.setRewardConfiguration(address(0), _emissionRate); } - function test_RevertIf_SetRewardConfigurationNotOwner( + function testFuzz_RevertIf_SetRewardConfigurationNotOwner( address _anyone, address _receiver, uint256 _emissionRate @@ -101,4 +101,85 @@ contract OwnableFunctions is RewardsDistributorTest { vm.prank(_anyone); rewardsDistributor.setRewardConfiguration(_receiver, _emissionRate); } + + function testFuzz_SetRewardTokenEmitEvent(address _token) public { + vm.assume(_token != address(0)); + + vm.expectEmit(); + emit RewardsDistributor.RewardTokenSet(_token); + rewardsDistributor.setRewardToken(_token); + } + + function testFuzz_SetRewardTokenWithdrawFunds( + address _token, + uint256 _depositAmount + ) public { + vm.assume(_token != address(0)); + + _depositAmount = bound( + _depositAmount, + 0, + govToken.balanceOf(address(this)) + ); + govToken.transfer(address(rewardsDistributor), _depositAmount); + + uint256 balanceBefore = govToken.balanceOf(address(this)); + + rewardsDistributor.setRewardToken(_token); + + assertEq( + govToken.balanceOf(address(this)), + balanceBefore + _depositAmount + ); + } + + function testFuzz_RevertIf_SetRewardTokenNotOwner( + address _anyone, + address _token + ) public { + vm.assume(_token != address(0)); + vm.assume(_anyone != address(this)); + + vm.expectRevert( + abi.encodeWithSelector( + Ownable.OwnableUnauthorizedAccount.selector, + _anyone + ) + ); + vm.prank(_anyone); + rewardsDistributor.setRewardToken(_token); + } + + function testFuzz_RevertIf_SetRewardTokenZeroAddress() public { + vm.expectRevert(RewardsDistributor.ZeroAddress.selector); + rewardsDistributor.setRewardToken(address(0)); + } + + function testFuzz_WithdrawFunds(address _to, uint256 _amount) public { + _amount = bound(_amount, 0, govToken.balanceOf(address(this))); + govToken.transfer(address(rewardsDistributor), _amount); + + uint256 balanceBefore = govToken.balanceOf(_to); + + rewardsDistributor.withdrawFunds(_to, _amount); + + assertEq(govToken.balanceOf(_to), balanceBefore + _amount); + } + + function testFuzz_RevertIf_WithdrawFundsNotOwner( + address _anyone, + address _to, + uint256 _amount + ) public { + vm.assume(_anyone != address(this)); + + vm.expectRevert( + abi.encodeWithSelector( + Ownable.OwnableUnauthorizedAccount.selector, + _anyone + ) + ); + vm.prank(_anyone); + rewardsDistributor.withdrawFunds(_to, _amount); + } } diff --git a/test/Staking.t.sol b/test/Staking.t.sol index bec71f1..a09dbf4 100644 --- a/test/Staking.t.sol +++ b/test/Staking.t.sol @@ -941,6 +941,10 @@ contract ClaimRewards is StakingTest { vm.expectRevert(Staking.KeyperHasNoShares.selector); staking.claimRewards(0); } + + // TODO when pool balance is > 0, time passes and the new depostior can't + // withdraw other users rewards + function testFuzz_RevertIf_NoRewardsToClaimToThatUser() public {} } contract Unstake is StakingTest { From 405dbde5e87b0a02c6d9ff6a18e72c3ed06e6df6 Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Sun, 30 Jun 2024 14:21:48 -0300 Subject: [PATCH 04/13] improve fuzz bounds --- src/RewardsDistributor.sol | 4 ++++ test/RewardsDistributor.t.sol | 24 +++++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/RewardsDistributor.sol b/src/RewardsDistributor.sol index 61e31b3..37bc9a9 100644 --- a/src/RewardsDistributor.sol +++ b/src/RewardsDistributor.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.26; +import {console} from "@forge-std/console.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {Ownable2StepUpgradeable} from "@openzeppelin-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; @@ -78,12 +79,15 @@ contract RewardsDistributor is Ownable2StepUpgradeable, IRewardsDistributor { // difference in time since last update uint256 timeDelta = block.timestamp - rewardConfiguration.lastUpdate; + console.log("time-delta", timeDelta); + if (rewardConfiguration.emissionRate == 0 || timeDelta == 0) { // nothing to do return 0; } rewards = rewardConfiguration.emissionRate * timeDelta; + console.log("rewards", rewards); // update the last update timestamp rewardConfiguration.lastUpdate = block.timestamp; diff --git a/test/RewardsDistributor.t.sol b/test/RewardsDistributor.t.sol index 8da1fad..2140609 100644 --- a/test/RewardsDistributor.t.sol +++ b/test/RewardsDistributor.t.sol @@ -28,6 +28,8 @@ contract RewardsDistributorTest is Test { } function _jumpAhead(uint256 _seconds) public { + vm.assume(_seconds != 0); + _seconds = bound(_seconds, 1, 26 weeks); vm.warp(vm.getBlockTimestamp() + _seconds); } @@ -35,7 +37,7 @@ contract RewardsDistributorTest is Test { address receiver, uint256 emissionRate ) internal { - emissionRate = bound(emissionRate, 0, 1e18); + emissionRate = bound(emissionRate, 1, 1e18); rewardsDistributor.setRewardConfiguration(receiver, emissionRate); } } @@ -45,6 +47,8 @@ contract OwnableFunctions is RewardsDistributorTest { address _receiver, uint256 _emissionRate ) public { + vm.assume(_receiver != address(0)); + vm.expectEmit(); emit RewardsDistributor.RewardConfigurationSet( _receiver, @@ -57,6 +61,8 @@ contract OwnableFunctions is RewardsDistributorTest { address _receiver, uint256 _emissionRate ) public { + vm.assume(_receiver != address(0)); + rewardsDistributor.setRewardConfiguration(_receiver, _emissionRate); (uint256 emissionRate, ) = rewardsDistributor.rewardConfigurations( @@ -70,6 +76,8 @@ contract OwnableFunctions is RewardsDistributorTest { address _receiver, uint256 _emissionRate ) public { + vm.assume(_receiver != address(0)); + rewardsDistributor.setRewardConfiguration(_receiver, _emissionRate); (, uint256 lastUpdate) = rewardsDistributor.rewardConfigurations( @@ -156,6 +164,8 @@ contract OwnableFunctions is RewardsDistributorTest { } function testFuzz_WithdrawFunds(address _to, uint256 _amount) public { + vm.assume(_to != address(0)); + _amount = bound(_amount, 0, govToken.balanceOf(address(this))); govToken.transfer(address(rewardsDistributor), _amount); @@ -183,3 +193,15 @@ contract OwnableFunctions is RewardsDistributorTest { rewardsDistributor.withdrawFunds(_to, _amount); } } + +contract CollectRewards is RewardsDistributorTest { + // function testFuzz_CollectRewardsReturnsRewardAmount(address _receiver, uint256 _jump, uint256 _emisisonRate) public{ + // _setRewardConfiguration(_receiver, _emisisonRate); + // uint256 timestampBefore = vm.getBlockTimestamp(); + // _jumpAhead(_jump); + // uint256 expectedRewards = _emisisonRate * (block.timestamp - timestampBefore); + // vm.prank(_receiver); + // uint256 rewards = rewardsDistributor.collectRewards(); + // assertEq(rewards, expectedRewards); + // } +} From add2c5615a65920cc1e404511041f68288ab78a2 Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Sun, 30 Jun 2024 17:32:11 -0300 Subject: [PATCH 05/13] collect rewards --- src/RewardsDistributor.sol | 4 -- test/RewardsDistributor.t.sol | 129 ++++++++++++++++++++++++++++------ 2 files changed, 109 insertions(+), 24 deletions(-) diff --git a/src/RewardsDistributor.sol b/src/RewardsDistributor.sol index 37bc9a9..61e31b3 100644 --- a/src/RewardsDistributor.sol +++ b/src/RewardsDistributor.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.26; -import {console} from "@forge-std/console.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {Ownable2StepUpgradeable} from "@openzeppelin-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; @@ -79,15 +78,12 @@ contract RewardsDistributor is Ownable2StepUpgradeable, IRewardsDistributor { // difference in time since last update uint256 timeDelta = block.timestamp - rewardConfiguration.lastUpdate; - console.log("time-delta", timeDelta); - if (rewardConfiguration.emissionRate == 0 || timeDelta == 0) { // nothing to do return 0; } rewards = rewardConfiguration.emissionRate * timeDelta; - console.log("rewards", rewards); // update the last update timestamp rewardConfiguration.lastUpdate = block.timestamp; diff --git a/test/RewardsDistributor.t.sol b/test/RewardsDistributor.t.sol index 2140609..55f1296 100644 --- a/test/RewardsDistributor.t.sol +++ b/test/RewardsDistributor.t.sol @@ -17,7 +17,6 @@ contract RewardsDistributorTest is Test { _jumpAhead(1234); govToken = new MockGovToken(); - govToken.mint(address(this), 1_000_000_000e18); vm.label(address(govToken), "govToken"); // deploy rewards distributor @@ -25,6 +24,8 @@ contract RewardsDistributorTest is Test { address(this), address(govToken) ); + + govToken.mint(address(rewardsDistributor), 20_000_000e18); } function _jumpAhead(uint256 _seconds) public { @@ -32,14 +33,6 @@ contract RewardsDistributorTest is Test { _seconds = bound(_seconds, 1, 26 weeks); vm.warp(vm.getBlockTimestamp() + _seconds); } - - function _setRewardConfiguration( - address receiver, - uint256 emissionRate - ) internal { - emissionRate = bound(emissionRate, 1, 1e18); - rewardsDistributor.setRewardConfiguration(receiver, emissionRate); - } } contract OwnableFunctions is RewardsDistributorTest { @@ -131,14 +124,19 @@ contract OwnableFunctions is RewardsDistributorTest { ); govToken.transfer(address(rewardsDistributor), _depositAmount); - uint256 balanceBefore = govToken.balanceOf(address(this)); + uint256 balanceBeforeOwner = govToken.balanceOf(address(this)); + uint256 balanceBeforeContract = govToken.balanceOf( + address(rewardsDistributor) + ); rewardsDistributor.setRewardToken(_token); assertEq( govToken.balanceOf(address(this)), - balanceBefore + _depositAmount + balanceBeforeOwner + balanceBeforeContract ); + + assertEq(govToken.balanceOf(address(rewardsDistributor)), 0); } function testFuzz_RevertIf_SetRewardTokenNotOwner( @@ -195,13 +193,104 @@ contract OwnableFunctions is RewardsDistributorTest { } contract CollectRewards is RewardsDistributorTest { - // function testFuzz_CollectRewardsReturnsRewardAmount(address _receiver, uint256 _jump, uint256 _emisisonRate) public{ - // _setRewardConfiguration(_receiver, _emisisonRate); - // uint256 timestampBefore = vm.getBlockTimestamp(); - // _jumpAhead(_jump); - // uint256 expectedRewards = _emisisonRate * (block.timestamp - timestampBefore); - // vm.prank(_receiver); - // uint256 rewards = rewardsDistributor.collectRewards(); - // assertEq(rewards, expectedRewards); - // } + function testFuzz_CollectRewardsReturnsRewardAmount( + address _receiver, + uint256 _jump, + uint256 _emissionRate + ) public { + vm.assume(address(_receiver) != address(0)); + + _emissionRate = bound(_emissionRate, 1, 1e18); + rewardsDistributor.setRewardConfiguration(_receiver, _emissionRate); + + uint256 timestampBefore = vm.getBlockTimestamp(); + + _jumpAhead(_jump); + + uint256 expectedRewards = _emissionRate * + (vm.getBlockTimestamp() - timestampBefore); + + vm.prank(_receiver); + uint256 rewards = rewardsDistributor.collectRewards(); + + assertEq(rewards, expectedRewards); + } + + function testFuzz_CollectRewardsEmitEvent( + address _receiver, + uint256 _jump, + uint256 _emissionRate + ) public { + vm.assume(address(_receiver) != address(0)); + + _emissionRate = bound(_emissionRate, 1, 1e18); + rewardsDistributor.setRewardConfiguration(_receiver, _emissionRate); + + uint256 timestampBefore = vm.getBlockTimestamp(); + + _jumpAhead(_jump); + + vm.expectEmit(); + emit RewardsDistributor.RewardCollected( + _receiver, + _emissionRate * (vm.getBlockTimestamp() - timestampBefore) + ); + + vm.prank(_receiver); + rewardsDistributor.collectRewards(); + } + + function testFuzz_CollectRewardsTransferTokens( + address _receiver, + uint256 _jump, + uint256 _emissionRate + ) public { + vm.assume(address(_receiver) != address(0)); + + _emissionRate = bound(_emissionRate, 1, 1e18); + rewardsDistributor.setRewardConfiguration(_receiver, _emissionRate); + + uint256 timestampBefore = vm.getBlockTimestamp(); + + _jumpAhead(_jump); + + uint256 expectedRewards = _emissionRate * + (vm.getBlockTimestamp() - timestampBefore); + + vm.prank(_receiver); + rewardsDistributor.collectRewards(); + + assertEq(govToken.balanceOf(_receiver), expectedRewards); + } + + function testFuzz_CollectRewardsReturnZeroWhenEmissionRateIsZero( + address _receiver, + uint256 _jump + ) public { + vm.assume(address(_receiver) != address(0)); + + rewardsDistributor.setRewardConfiguration(_receiver, 0); + + uint256 timestampBefore = vm.getBlockTimestamp(); + + _jumpAhead(_jump); + + vm.prank(_receiver); + uint256 rewards = rewardsDistributor.collectRewards(); + + assertEq(rewards, 0); + } + + function testFuzz_CollectRewardsReturnZeroWhenTimeDeltaIsZero( + address _receiver + ) public { + vm.assume(address(_receiver) != address(0)); + + rewardsDistributor.setRewardConfiguration(_receiver, 1); + + vm.prank(_receiver); + uint256 rewards = rewardsDistributor.collectRewards(); + + assertEq(rewards, 0); + } } From b0522283d522de031e9c18e66814f06f9f9f6ac1 Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Sun, 30 Jun 2024 17:36:11 -0300 Subject: [PATCH 06/13] lint --- .solhintrc | 3 ++- lint.yml | 44 -------------------------------------- test/helper/ProxyUtils.sol | 4 ++-- 3 files changed, 4 insertions(+), 47 deletions(-) delete mode 100644 lint.yml diff --git a/.solhintrc b/.solhintrc index 002a423..a4a9806 100644 --- a/.solhintrc +++ b/.solhintrc @@ -7,6 +7,7 @@ "no-inline-assembly": "off", "no-empty-blocks": "off", "no-global-import": "off", - gas-custom-errors": "off", + "gas-custom-errors": "off", + "func-name-mixedcase": "off } } diff --git a/lint.yml b/lint.yml deleted file mode 100644 index 2da1b74..0000000 --- a/lint.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: "Lint" - -env: - FOUNDRY_PROFILE: "ci" - -on: - workflow_dispatch: - pull_request: - push: - branches: - - "main" - -jobs: - lint: - runs-on: ubuntu-latest - steps: - - name: "Check out the repo" - uses: actions/checkout@v3 - with: - submodules: recursive - - name: "Install Node.js" - uses: actions/setup-node@v3 - with: - node-version: lts/* - - - name: "Install the Node.js dependencies" - run: npm install - - - name: Run linter and check for errors - id: lint - run: | - LINT_OUTCOME=$(npm run lint 2>&1 || true) # Prevent the step from failing immediately - echo "$LINT_OUTCOME" - echo "LINT_OUTCOME<> $GITHUB_ENV - echo "$LINT_OUTCOME" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - if echo "$LINT_OUTCOME" | grep -q " error "; then - echo "## Lint result" >> $GITHUB_STEP_SUMMARY - echo "❌ Failed due to errors" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "## Lint result" >> $GITHUB_STEP_SUMMARY - echo "✅ Passed or warnings found" >> $GITHUB_STEP_SUMMARY - fi diff --git a/test/helper/ProxyUtils.sol b/test/helper/ProxyUtils.sol index 42a04d8..a30402d 100644 --- a/test/helper/ProxyUtils.sol +++ b/test/helper/ProxyUtils.sol @@ -5,9 +5,9 @@ import {Vm} from "@forge-std/Vm.sol"; import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol"; library ProxyUtils { - address constant CHEATCODE_ADDRESS = + address public constant CHEATCODE_ADDRESS = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; - Vm constant vm = Vm(CHEATCODE_ADDRESS); + Vm public vm = Vm(CHEATCODE_ADDRESS); function getAdminAddress(address proxy) public view returns (address) { bytes32 adminSlot = vm.load(proxy, ERC1967Utils.ADMIN_SLOT); From d47c4edbf889c8467e02839d60b39e62652edd79 Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Sun, 30 Jun 2024 17:37:55 -0300 Subject: [PATCH 07/13] compile --- src/RewardsDistributor.sol | 1 - test/helper/ProxyUtils.sol | 6 ++++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/RewardsDistributor.sol b/src/RewardsDistributor.sol index 61e31b3..b3120c1 100644 --- a/src/RewardsDistributor.sol +++ b/src/RewardsDistributor.sol @@ -17,7 +17,6 @@ contract RewardsDistributor is Ownable2StepUpgradeable, IRewardsDistributor { //////////////////////////////////////////////////////////////*/ /// @notice the reward token, i.e. SHU - /// @dev set in initialize, can't be changed IERC20 public rewardToken; /*////////////////////////////////////////////////////////////// diff --git a/test/helper/ProxyUtils.sol b/test/helper/ProxyUtils.sol index a30402d..1484550 100644 --- a/test/helper/ProxyUtils.sol +++ b/test/helper/ProxyUtils.sol @@ -7,10 +7,12 @@ import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.s library ProxyUtils { address public constant CHEATCODE_ADDRESS = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D; - Vm public vm = Vm(CHEATCODE_ADDRESS); function getAdminAddress(address proxy) public view returns (address) { - bytes32 adminSlot = vm.load(proxy, ERC1967Utils.ADMIN_SLOT); + bytes32 adminSlot = Vm(CHEATCODE_ADDRESS).load( + proxy, + ERC1967Utils.ADMIN_SLOT + ); return address(uint160(uint256(adminSlot))); } } From 315616a2838c6792db3ef0901bb781a029df563c Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Sun, 30 Jun 2024 17:46:59 -0300 Subject: [PATCH 08/13] test constructor --- src/Staking.sol | 6 ++++-- test/RewardsDistributor.t.sol | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Staking.sol b/src/Staking.sol index cc7ee4a..256993a 100644 --- a/src/Staking.sol +++ b/src/Staking.sol @@ -186,6 +186,8 @@ contract Staking is ERC20VotesUpgradeable, Ownable2StepUpgradeable { rewardsDistributor = IRewardsDistributor(_rewardsDistributor); lockPeriod = _lockPeriod; minStake = _minStake; + + nextStakeId = 1; } /// @notice Stake SHU @@ -199,7 +201,7 @@ contract Staking is ERC20VotesUpgradeable, Ownable2StepUpgradeable { /// TODO slippage protection function stake( uint256 amount - ) external onlyKeyper updateRewards returns (uint256) { + ) external onlyKeyper updateRewards returns (uint256 stakeId) { /////////////////////////// CHECKS /////////////////////////////// require(amount > 0, ZeroAmount()); @@ -224,7 +226,7 @@ contract Staking is ERC20VotesUpgradeable, Ownable2StepUpgradeable { _mint(keyper, sharesToMint); // Get next stake id and increment it - uint256 stakeId = ++nextStakeId; + stakeId = nextStakeId++; stakes[stakeId] = Stake({ amount: amount, diff --git a/test/RewardsDistributor.t.sol b/test/RewardsDistributor.t.sol index 55f1296..cd1108b 100644 --- a/test/RewardsDistributor.t.sol +++ b/test/RewardsDistributor.t.sol @@ -35,6 +35,13 @@ contract RewardsDistributorTest is Test { } } +contract Constructor is RewardsDistributorTest { + function test_SetUp() public { + assertEq(address(rewardsDistributor.rewardToken()), address(govToken)); + assertEq(Ownable(address(rewardsDistributor)).owner(), address(this)); + } +} + contract OwnableFunctions is RewardsDistributorTest { function testFuzz_SetRewardConfigurationEmitEvent( address _receiver, From 5d688f47e7a4c428a70e64f81a5c1602346efe61 Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Sun, 30 Jun 2024 17:50:34 -0300 Subject: [PATCH 09/13] natspec --- src/Staking.sol | 2 +- test/RewardsDistributor.t.sol | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Staking.sol b/src/Staking.sol index 256993a..7494f91 100644 --- a/src/Staking.sol +++ b/src/Staking.sol @@ -197,7 +197,7 @@ contract Staking is ERC20VotesUpgradeable, Ownable2StepUpgradeable { /// - The shares are non-transferable /// - Only keypers can stake /// @param amount The amount of SHU to stake - /// @return The index of the stake + /// @return stakeId The index of the stake /// TODO slippage protection function stake( uint256 amount diff --git a/test/RewardsDistributor.t.sol b/test/RewardsDistributor.t.sol index cd1108b..9136e9c 100644 --- a/test/RewardsDistributor.t.sol +++ b/test/RewardsDistributor.t.sol @@ -36,7 +36,7 @@ contract RewardsDistributorTest is Test { } contract Constructor is RewardsDistributorTest { - function test_SetUp() public { + function test_SetUp() public view { assertEq(address(rewardsDistributor.rewardToken()), address(govToken)); assertEq(Ownable(address(rewardsDistributor)).owner(), address(this)); } @@ -278,8 +278,6 @@ contract CollectRewards is RewardsDistributorTest { rewardsDistributor.setRewardConfiguration(_receiver, 0); - uint256 timestampBefore = vm.getBlockTimestamp(); - _jumpAhead(_jump); vm.prank(_receiver); From 9412d82a47c958ee245850181bcc718acd8a5fd8 Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Sun, 30 Jun 2024 18:05:32 -0300 Subject: [PATCH 10/13] add StakingHarness --- src/RewardsDistributor.sol | 15 +++++++-------- src/Staking.sol | 2 +- test/Staking.t.sol | 24 +++++++++++++++++------- test/{helper => helpers}/ProxyUtils.sol | 0 test/helpers/StakingHarness.sol | 10 ++++++++++ 5 files changed, 35 insertions(+), 16 deletions(-) rename test/{helper => helpers}/ProxyUtils.sol (100%) create mode 100644 test/helpers/StakingHarness.sol diff --git a/src/RewardsDistributor.sol b/src/RewardsDistributor.sol index b3120c1..1da8d2e 100644 --- a/src/RewardsDistributor.sol +++ b/src/RewardsDistributor.sol @@ -3,12 +3,13 @@ pragma solidity 0.8.26; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import {Ownable2StepUpgradeable} from "@openzeppelin-upgradeable/contracts/access/Ownable2StepUpgradeable.sol"; +import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IRewardsDistributor} from "./interfaces/IRewardsDistributor.sol"; -contract RewardsDistributor is Ownable2StepUpgradeable, IRewardsDistributor { +contract RewardsDistributor is Ownable2Step, IRewardsDistributor { /*////////////////////////////////////////////////////////////// - LIBRARIES + LIBRARIES //////////////////////////////////////////////////////////////*/ using SafeERC20 for IERC20; @@ -30,7 +31,7 @@ contract RewardsDistributor is Ownable2StepUpgradeable, IRewardsDistributor { } /*////////////////////////////////////////////////////////////// - MAPPINGS/ARRAYS + MAPPINGS //////////////////////////////////////////////////////////////*/ mapping(address receiver => RewardConfiguration configuration) @@ -58,10 +59,8 @@ contract RewardsDistributor is Ownable2StepUpgradeable, IRewardsDistributor { /// @notice Initialize the contract /// @param newOwner The owner of the contract, i.e. the DAO contract address - constructor(address newOwner, address _rewardToken) { - // Transfer ownership to the DAO contract - _transferOwnership(newOwner); - + /// @param _rewardToken The reward token, i.e. SHU + constructor(address newOwner, address _rewardToken) Ownable(newOwner) { // Set the reward token rewardToken = IERC20(_rewardToken); } diff --git a/src/Staking.sol b/src/Staking.sol index 7494f91..c30e817 100644 --- a/src/Staking.sol +++ b/src/Staking.sol @@ -40,7 +40,7 @@ contract Staking is ERC20VotesUpgradeable, Ownable2StepUpgradeable { uint256 public minStake; /// @notice Unique identifier that will be used for the next stake. - uint256 private nextStakeId; + uint256 internal nextStakeId; /*////////////////////////////////////////////////////////////// STRUCTS diff --git a/test/Staking.t.sol b/test/Staking.t.sol index a09dbf4..0c4e70b 100644 --- a/test/Staking.t.sol +++ b/test/Staking.t.sol @@ -9,12 +9,13 @@ import {Staking} from "src/Staking.sol"; import {RewardsDistributor} from "src/RewardsDistributor.sol"; import {IRewardsDistributor} from "src/interfaces/IRewardsDistributor.sol"; import {MockGovToken} from "test/mocks/MockGovToken.sol"; -import {ProxyUtils} from "test/helper/ProxyUtils.sol"; +import {ProxyUtils} from "test/helpers/ProxyUtils.sol"; +import {StakingHarness} from "test/helpers/StakingHarness.sol"; contract StakingTest is Test { using FixedPointMathLib for uint256; - Staking public staking; + StakingHarness public staking; IRewardsDistributor public rewardsDistributor; MockGovToken public govToken; @@ -37,9 +38,9 @@ contract StakingTest is Test { ); // deploy staking - address stakingImpl = address(new Staking()); + address stakingImpl = address(new StakingHarness()); - staking = Staking( + staking = StakingHarness( address( new TransparentUpgradeableProxy(stakingImpl, address(this), "") ) @@ -54,8 +55,6 @@ contract StakingTest is Test { MIN_STAKE ); - staking = Staking(staking); - rewardsDistributor.setRewardConfiguration( address(staking), REWARD_RATE @@ -182,12 +181,19 @@ contract Stake is StakingTest { vm.startPrank(_depositor); govToken.approve(address(staking), _amount); + uint256 expectedStakeId = staking.exposed_nextStakeId(); + uint256 stakeId = staking.stake(_amount); - assertGt(stakeId, 0, "Wrong stake id"); + assertEq(stakeId, expectedStakeId, "Wrong stake id"); vm.stopPrank(); } + function testFuzz_IncreaseNextStakeId( + address _depositor, + uint256 _amount + ) public {} + function testFuzz_TransferTokensWhenStaking( address _depositor, uint256 _amount @@ -593,6 +599,10 @@ contract Stake is StakingTest { } function testFuzz_RevertIf_ZeroAmount(address _depositor) public { + vm.assume( + _depositor != address(0) && + _depositor != ProxyUtils.getAdminAddress(address(staking)) + ); _setKeyper(_depositor, true); vm.assume(_depositor != address(0)); diff --git a/test/helper/ProxyUtils.sol b/test/helpers/ProxyUtils.sol similarity index 100% rename from test/helper/ProxyUtils.sol rename to test/helpers/ProxyUtils.sol diff --git a/test/helpers/StakingHarness.sol b/test/helpers/StakingHarness.sol new file mode 100644 index 0000000..38364ea --- /dev/null +++ b/test/helpers/StakingHarness.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import {Staking} from "src/Staking.sol"; + +contract StakingHarness is Staking { + function exposed_nextStakeId() external view returns (uint256) { + return nextStakeId; + } +} From 852da7db07cd286a3021a713d86e48633edf28d2 Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Sun, 30 Jun 2024 19:04:42 -0300 Subject: [PATCH 11/13] coverage --- src/Staking.sol | 2 -- test/RewardsDistributor.t.sol | 2 +- test/Staking.t.sol | 2 ++ 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Staking.sol b/src/Staking.sol index c30e817..ce8a10c 100644 --- a/src/Staking.sol +++ b/src/Staking.sol @@ -242,8 +242,6 @@ contract Staking is ERC20VotesUpgradeable, Ownable2StepUpgradeable { stakingToken.safeTransferFrom(keyper, address(this), amount); emit Staked(keyper, amount, sharesToMint, lockPeriod); - - return stakeId; } /// @notice Unstake SHU diff --git a/test/RewardsDistributor.t.sol b/test/RewardsDistributor.t.sol index 9136e9c..bece286 100644 --- a/test/RewardsDistributor.t.sol +++ b/test/RewardsDistributor.t.sol @@ -37,7 +37,7 @@ contract RewardsDistributorTest is Test { contract Constructor is RewardsDistributorTest { function test_SetUp() public view { - assertEq(address(rewardsDistributor.rewardToken()), address(govToken)); + assertEq(rewardsDistributor.rewardToken(), address(govToken)); assertEq(Ownable(address(rewardsDistributor)).owner(), address(this)); } } diff --git a/test/Staking.t.sol b/test/Staking.t.sol index 0c4e70b..e4a2913 100644 --- a/test/Staking.t.sol +++ b/test/Staking.t.sol @@ -150,6 +150,8 @@ contract Initializer is StakingTest { ); assertEq(staking.lockPeriod(), LOCK_PERIOD, "Wrong lock period"); assertEq(staking.minStake(), MIN_STAKE, "Wrong min stake"); + + assertEq(staking.exposed_nextStakeId(), 1); } function test_RevertIf_InitializeImplementation() public { From e1e3356ae96475e7c535b6f83507a6811991e921 Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Sun, 30 Jun 2024 19:06:02 -0300 Subject: [PATCH 12/13] compile --- test/RewardsDistributor.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/RewardsDistributor.t.sol b/test/RewardsDistributor.t.sol index bece286..9136e9c 100644 --- a/test/RewardsDistributor.t.sol +++ b/test/RewardsDistributor.t.sol @@ -37,7 +37,7 @@ contract RewardsDistributorTest is Test { contract Constructor is RewardsDistributorTest { function test_SetUp() public view { - assertEq(rewardsDistributor.rewardToken(), address(govToken)); + assertEq(address(rewardsDistributor.rewardToken()), address(govToken)); assertEq(Ownable(address(rewardsDistributor)).owner(), address(this)); } } From 8d91d8573831993d6f77aa973933744fedb7f819 Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Sun, 30 Jun 2024 19:18:55 -0300 Subject: [PATCH 13/13] testFuzz_SetOwnerAndRewardTokenToArbitraryAddress --- test/RewardsDistributor.t.sol | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/test/RewardsDistributor.t.sol b/test/RewardsDistributor.t.sol index 9136e9c..071d4ad 100644 --- a/test/RewardsDistributor.t.sol +++ b/test/RewardsDistributor.t.sol @@ -36,10 +36,24 @@ contract RewardsDistributorTest is Test { } contract Constructor is RewardsDistributorTest { - function test_SetUp() public view { + function test_SetOwnerAndRewardToken() public view { assertEq(address(rewardsDistributor.rewardToken()), address(govToken)); assertEq(Ownable(address(rewardsDistributor)).owner(), address(this)); } + + function testFuzz_SetOwnerAndRewardTokenToArbitraryAddress( + address _owner, + address _rewardToken + ) public { + vm.assume(_owner != address(0)); + + RewardsDistributor rewardsDistributor = new RewardsDistributor( + _owner, + _rewardToken + ); + assertEq(address(rewardsDistributor.rewardToken()), _rewardToken); + assertEq(Ownable(address(rewardsDistributor)).owner(), _owner); + } } contract OwnableFunctions is RewardsDistributorTest {