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/src/RewardsDistributor.sol b/src/RewardsDistributor.sol index 45d2016..1da8d2e 100644 --- a/src/RewardsDistributor.sol +++ b/src/RewardsDistributor.sol @@ -1,24 +1,25 @@ // 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 {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {IRewardsDistributor} from "./interfaces/IRewardsDistributor.sol"; -interface IRewardsDistributor { - function distributeRewards() external; -} - -// TODO should be pausable? -contract RewardsDistributor is Ownable2StepUpgradeable { +contract RewardsDistributor is Ownable2Step, IRewardsDistributor { /*////////////////////////////////////////////////////////////// - LIBRARIES + LIBRARIES //////////////////////////////////////////////////////////////*/ using SafeERC20 for IERC20; + /*////////////////////////////////////////////////////////////// + VARIABLES + //////////////////////////////////////////////////////////////*/ + + /// @notice the reward token, i.e. SHU + IERC20 public rewardToken; + /*////////////////////////////////////////////////////////////// STRUCTS //////////////////////////////////////////////////////////////*/ @@ -30,128 +31,105 @@ contract RewardsDistributor is Ownable2StepUpgradeable { } /*////////////////////////////////////////////////////////////// - MAPPINGS/ARRAYS + MAPPINGS //////////////////////////////////////////////////////////////*/ - 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(); - - // Transfer ownership to the DAO contract - _transferOwnership(newOwner); - } + event RewardTokenSet(address indexed rewardToken); - /// @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); - } + /*////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ - rewardConfigurations[receiver][token] = RewardConfiguration( - emissionRate, - block.timestamp - ); + /// @notice Thrown when address is zero + error ZeroAddress(); - 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); + /// @notice Initialize the contract + /// @param newOwner The owner of the contract, i.e. the DAO contract address + /// @param _rewardToken The reward token, i.e. SHU + constructor(address newOwner, address _rewardToken) Ownable(newOwner) { + // 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), ZeroAddress()); + + rewardConfigurations[receiver] = RewardConfiguration( + emissionRate, + block.timestamp + ); + + emit RewardConfigurationSet(receiver, emissionRate); + } - emit RewardDistributed(receiver, token, reward); + /// @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 + ) public override onlyOwner { + rewardToken.safeTransfer(to, amount); } - function getRewardTokens( - address receiver - ) external view returns (address[] memory) { - return rewardTokens[receiver]; + /// @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))); + + // set the new reward token + rewardToken = IERC20(_rewardToken); + + emit RewardTokenSet(_rewardToken); } } diff --git a/src/Staking.sol b/src/Staking.sol index 575de7a..ce8a10c 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 @@ -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 @@ -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(); _; } @@ -186,6 +186,8 @@ contract Staking is ERC20VotesUpgradeable, Ownable2StepUpgradeable { rewardsDistributor = IRewardsDistributor(_rewardsDistributor); lockPeriod = _lockPeriod; minStake = _minStake; + + nextStakeId = 1; } /// @notice Stake SHU @@ -195,11 +197,11 @@ 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 - ) 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, @@ -240,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/src/interfaces/IRewardsDistributor.sol b/src/interfaces/IRewardsDistributor.sol index 7d9a8ef..60c05f9 100644 --- a/src/interfaces/IRewardsDistributor.sol +++ b/src/interfaces/IRewardsDistributor.sol @@ -2,22 +2,14 @@ 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); + function setRewardToken(address _rewardToken) external; } diff --git a/test/RewardsDistributor.t.sol b/test/RewardsDistributor.t.sol new file mode 100644 index 0000000..071d4ad --- /dev/null +++ b/test/RewardsDistributor.t.sol @@ -0,0 +1,315 @@ +// 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(); + vm.label(address(govToken), "govToken"); + + // deploy rewards distributor + rewardsDistributor = new RewardsDistributor( + address(this), + address(govToken) + ); + + govToken.mint(address(rewardsDistributor), 20_000_000e18); + } + + function _jumpAhead(uint256 _seconds) public { + vm.assume(_seconds != 0); + _seconds = bound(_seconds, 1, 26 weeks); + vm.warp(vm.getBlockTimestamp() + _seconds); + } +} + +contract Constructor is RewardsDistributorTest { + 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 { + function testFuzz_SetRewardConfigurationEmitEvent( + address _receiver, + uint256 _emissionRate + ) public { + vm.assume(_receiver != address(0)); + + vm.expectEmit(); + emit RewardsDistributor.RewardConfigurationSet( + _receiver, + _emissionRate + ); + rewardsDistributor.setRewardConfiguration(_receiver, _emissionRate); + } + + function testFuzz_SetRewardConfigurationSetEmissionRate( + address _receiver, + uint256 _emissionRate + ) public { + vm.assume(_receiver != address(0)); + + rewardsDistributor.setRewardConfiguration(_receiver, _emissionRate); + + (uint256 emissionRate, ) = rewardsDistributor.rewardConfigurations( + _receiver + ); + + assertEq(emissionRate, _emissionRate); + } + + function testFuzz_SetRewardConfigurationSetLastUpdate( + address _receiver, + uint256 _emissionRate + ) public { + vm.assume(_receiver != address(0)); + + rewardsDistributor.setRewardConfiguration(_receiver, _emissionRate); + + (, uint256 lastUpdate) = rewardsDistributor.rewardConfigurations( + _receiver + ); + assertEq(lastUpdate, vm.getBlockTimestamp()); + } + + function testFuzz_RevertIf_SetRewardConfigurationZeroAddress( + uint256 _emissionRate + ) public { + vm.expectRevert(RewardsDistributor.ZeroAddress.selector); + rewardsDistributor.setRewardConfiguration(address(0), _emissionRate); + } + + function testFuzz_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); + } + + 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 balanceBeforeOwner = govToken.balanceOf(address(this)); + uint256 balanceBeforeContract = govToken.balanceOf( + address(rewardsDistributor) + ); + + rewardsDistributor.setRewardToken(_token); + + assertEq( + govToken.balanceOf(address(this)), + balanceBeforeOwner + balanceBeforeContract + ); + + assertEq(govToken.balanceOf(address(rewardsDistributor)), 0); + } + + 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 { + vm.assume(_to != address(0)); + + _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); + } +} + +contract CollectRewards is RewardsDistributorTest { + 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); + + _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); + } +} diff --git a/test/Staking.t.sol b/test/Staking.t.sol index 862ad0b..e4a2913 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; @@ -33,22 +34,13 @@ 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 - address stakingImpl = address(new Staking()); + address stakingImpl = address(new StakingHarness()); - staking = Staking( + staking = StakingHarness( address( new TransparentUpgradeableProxy(stakingImpl, address(this), "") ) @@ -63,11 +55,8 @@ contract StakingTest is Test { MIN_STAKE ); - staking = Staking(staking); - rewardsDistributor.setRewardConfiguration( address(staking), - address(govToken), REWARD_RATE ); @@ -161,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 { @@ -192,12 +183,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 @@ -603,6 +601,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)); @@ -951,6 +953,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 { @@ -1404,6 +1410,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 { diff --git a/test/helper/ProxyUtils.sol b/test/helpers/ProxyUtils.sol similarity index 69% rename from test/helper/ProxyUtils.sol rename to test/helpers/ProxyUtils.sol index 42a04d8..1484550 100644 --- a/test/helper/ProxyUtils.sol +++ b/test/helpers/ProxyUtils.sol @@ -5,12 +5,14 @@ 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); 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))); } } 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; + } +}