From a9832693bf7fd488101bd9548bad3a9bc7565313 Mon Sep 17 00:00:00 2001 From: Ana Julia Date: Sun, 21 Jul 2024 20:44:45 -0300 Subject: [PATCH] delegate test setup --- src/DelegateStaking.sol | 2 +- test/DelegateStaking.t.sol | 184 ++++++++++++++++++++++++ test/Staking.t.sol | 12 -- test/helpers/DelegateStakingHarness.sol | 10 ++ 4 files changed, 195 insertions(+), 13 deletions(-) create mode 100644 test/DelegateStaking.t.sol create mode 100644 test/helpers/DelegateStakingHarness.sol diff --git a/src/DelegateStaking.sol b/src/DelegateStaking.sol index 87a42f7..b7b3be0 100644 --- a/src/DelegateStaking.sol +++ b/src/DelegateStaking.sol @@ -16,7 +16,7 @@ interface IStaking { /// @notice Shutter Delegate Staking Contract /// Allows users to stake SHU and earn rewards in exchange. -contract Delegate is ERC20VotesUpgradeable, OwnableUpgradeable { +contract DelegateStaking is ERC20VotesUpgradeable, OwnableUpgradeable { /*////////////////////////////////////////////////////////////// LIBRARIES //////////////////////////////////////////////////////////////*/ diff --git a/test/DelegateStaking.t.sol b/test/DelegateStaking.t.sol new file mode 100644 index 0000000..51ef5a1 --- /dev/null +++ b/test/DelegateStaking.t.sol @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import "@forge-std/Test.sol"; +import {TransparentUpgradeableProxy, ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +import {Staking} from "src/Staking.sol"; +import {DelegateStaking} from "src/DelegateStaking.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/helpers/ProxyUtils.sol"; +import {DelegateStakingHarness} from "test/helpers/DelegateStakingHarness.sol"; + +contract DelegateStakingTest is Test { + DelegateStakingHarness public delegate; + IRewardsDistributor public rewardsDistributor; + Staking public staking; + MockGovToken public govToken; + + uint256 constant LOCK_PERIOD = 182 days; // 6 months + uint256 constant REWARD_RATE = 0.1e18; + + 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(); + _mintGovToken(address(this), 100_000_000e18); + vm.label(address(govToken), "govToken"); + + // deploy rewards distributor + rewardsDistributor = IRewardsDistributor( + new RewardsDistributor(address(this), address(govToken)) + ); + + // deploy staking + address stakingImpl = address(new Staking()); + + staking = Staking( + address( + new TransparentUpgradeableProxy(stakingImpl, address(this), "") + ) + ); + vm.label(address(staking), "staking"); + + staking.initialize( + address(this), // owner + address(govToken), + address(rewardsDistributor), + 0, + 0 + ); + + address delegateImpl = address(new DelegateStakingHarness()); + + delegate = DelegateStakingHarness( + address( + new TransparentUpgradeableProxy(delegateImpl, address(this), "") + ) + ); + vm.label(address(delegate), "staking"); + + delegate.initialize( + address(this), // owner + address(govToken), + address(rewardsDistributor), + address(staking), + LOCK_PERIOD + ); + + rewardsDistributor.setRewardConfiguration( + address(staking), + REWARD_RATE + ); + + // fund reward distribution + govToken.transfer(address(rewardsDistributor), 100_000_000e18); + } + + function _jumpAhead(uint256 _seconds) public { + vm.warp(vm.getBlockTimestamp() + _seconds); + } + + function _boundMintAmount(uint96 _amount) internal pure returns (uint256) { + return bound(_amount, 0, 10_000_000e18); + } + + function _boundRealisticTimeAhead( + uint256 _time + ) internal pure returns (uint256) { + return bound(_time, 1, 105 weeks); // two years + } + + function _boundUnlockedTime(uint256 _time) internal view returns (uint256) { + return bound(_time, vm.getBlockTimestamp() + LOCK_PERIOD, 105 weeks); + } + + function _mintGovToken(address _to, uint256 _amount) internal { + vm.assume( + _to != address(0) && + _to != address(delegate) && + _to != ProxyUtils.getAdminAddress(address(delegate)) + ); + + govToken.mint(_to, _amount); + } + + function _boundToRealisticStake( + uint256 _stakeAmount + ) public pure returns (uint256 _boundedStakeAmount) { + _boundedStakeAmount = uint256( + bound(_stakeAmount, 100e18, 5_000_000e18) + ); + } + + function _stake( + address _user, + address _keyper, + uint256 _amount + ) internal returns (uint256 stakeId) { + vm.assume( + _keyper != address(0) && + _keyper != ProxyUtils.getAdminAddress(address(staking)) + ); + + vm.assume( + _user != address(0) && + _user != address(this) && + _user != address(delegate) && + _user != ProxyUtils.getAdminAddress(address(delegate)) && + _user != address(rewardsDistributor) + ); + + vm.startPrank(_user); + govToken.approve(address(delegate), _amount); + stakeId = delegate.stake(_keyper, _amount); + vm.stopPrank(); + } +} + +contract Initializer is DelegateStakingTest { + function test_Initialize() public view { + assertEq( + IERC20Metadata(address(delegate)).name(), + "Delegated Staking dSHU" + ); + assertEq(IERC20Metadata(address(delegate)).symbol(), "dSHU"); + assertEq(delegate.owner(), address(this), "Wrong owner"); + assertEq( + address(delegate.stakingToken()), + address(govToken), + "Wrong staking token" + ); + assertEq( + address(delegate.rewardsDistributor()), + address(rewardsDistributor), + "Wrong rewards distributor" + ); + assertEq(delegate.lockPeriod(), LOCK_PERIOD, "Wrong lock period"); + assertEq( + address(delegate.staking()), + address(staking), + "Wrong staking" + ); + + assertEq(delegate.exposed_nextStakeId(), 1); + } + + function test_RevertIf_InitializeImplementation() public { + DelegateStaking delegateImpl = new DelegateStaking(); + + vm.expectRevert(); + delegateImpl.initialize( + address(this), + address(govToken), + address(rewardsDistributor), + address(staking), + LOCK_PERIOD + ); + } +} diff --git a/test/Staking.t.sol b/test/Staking.t.sol index ae3ba0b..b159c77 100644 --- a/test/Staking.t.sol +++ b/test/Staking.t.sol @@ -155,18 +155,6 @@ contract StakingTest is Test { return _amount.mulDivDown(supply + 1, assets + 1); } - - function _assertMinRelativeLoss( - uint256 spent, - uint256 received, - uint256 minRelLoss, - string memory errorMessage - ) internal pure { - assertGt(spent, received, "Spent should be greater than received"); - - uint256 relativeLoss = ((spent - received) * 1e18) / spent; - assertGe(relativeLoss, minRelLoss, errorMessage); - } } contract Initializer is StakingTest { diff --git a/test/helpers/DelegateStakingHarness.sol b/test/helpers/DelegateStakingHarness.sol new file mode 100644 index 0000000..738570b --- /dev/null +++ b/test/helpers/DelegateStakingHarness.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import {DelegateStaking} from "src/DelegateStaking.sol"; + +contract DelegateStakingHarness is DelegateStaking { + function exposed_nextStakeId() external view returns (uint256) { + return nextStakeId; + } +}