diff --git a/src/test/WithdrawalMigration.t.sol b/src/test/WithdrawalMigration.t.sol index 491f18946..7b03a4852 100644 --- a/src/test/WithdrawalMigration.t.sol +++ b/src/test/WithdrawalMigration.t.sol @@ -2,7 +2,7 @@ pragma solidity =0.8.12; import "../test/EigenLayerTestHelper.t.sol"; -import "./unit/Utils.sol"; +import "src/test/utils/Utils.sol"; import "./mocks/ERC20Mock.sol"; ///@notice This set of tests shadow forks the contracts from M1, queues withdrawals, and tests the migration functionality diff --git a/src/test/tree/EigenPodManager.tree b/src/test/tree/EigenPodManager.tree new file mode 100644 index 000000000..695fbb0a8 --- /dev/null +++ b/src/test/tree/EigenPodManager.tree @@ -0,0 +1,93 @@ +├── EigenPodManager Tree (*** denotes that integrationt tests are needed to validate path) +├── when contract is deployed and initialized +│ └── it should properly set storage +├── when initialize called again +│ └── it should revert +├── when createPod called +│ ├── given the user has already created a pod +│ │ └── it should revert +│ ├── given that the max number of pods has been deployed +│ │ └── it should revert +│ └── given the user has not created a pod +│ └── it should deploy a pod +├── when stake is called +│ ├── given the user has not created a pod +│ │ └── it should deploy a pod +│ └── given the user has already created a pod +│ └── it should call stake on the eigenPod +├── when setMaxPods is called +│ ├── given the user is not the pauser +│ │ └── it should revert +│ └── given the user is the pauser +│ └── it should set the max pods +├── when updateBeaconChainOracle is called +│ ├── given the user is not the owner +│ │ └── it should revert +│ └── given the user is the owner +│ └── it should set the beacon chain oracle +├── when addShares is called +│ ├── given that the caller is not the delegationManager +│ │ └── it should revert +│ ├── given that the podOwner address is 0 +│ │ └── it should revert +│ ├── given that the shares amount is negative +│ │ └── it should revert +│ ├── given that the shares is not a whole gwei amount +│ │ └── it should revert +│ └── given that all of the above conditions are satisfied +│ └── it should update the podOwnerShares +├── when removeShares is called +│ ├── given that the caller is not the delegationManager +│ │ └── it should revert +│ ├── given that the shares amount is negative +│ │ └── it should revert +│ ├── given that the shares is not a whole gwei amount +│ │ └── it should revert +│ ├── given that removing shares results in the pod owner having negative shares +│ │ └── it should revert +│ └── given that all of the above conditions are satisfied +│ └── it should update the podOwnerShares +├── when withdrawSharesAsTokens is called +│ ├── given that the podOwner is address 0 +│ │ └── it should revert +│ ├── given that the destination is address 0 +│ │ └── it should revert +│ ├── given that the shares amount is negative +│ │ └── it should revert +│ ├── given that the shares is not a whole gwei amount +│ │ └── it should revert +│ ├── given that the current podOwner shares are negative +│ │ ├── given that the shares to withdraw are larger in magnitude than the shares of the podOwner +│ │ │ └── it should set the podOwnerShares to 0 and decrement shares to withdraw by the share deficit +│ │ └── given that the shares to withdraw are smaller in magnitude than shares of the podOwner +│ │ └── it should increment the podOwner shares by the shares to withdraw +│ └── given that the pod owner shares are positive +│ └── it should withdraw restaked ETH from the eigenPod +├── when shares are adjusted *** +│ ├── given that sharesBefore is negative or 0 +│ │ ├── given that sharesAfter is negative or zero +│ │ │ └── the change in delegateable shares should be 0 +│ │ └── given that sharesAfter is positive +│ │ └── the change in delegateable shares should be positive +│ └── given that sharesBefore is positive +│ ├── given that sharesAfter is negative or zero +│ │ └── the change in delegateable shares is negative sharesBefore +│ └── given that sharesAfter is positive +│ └── the change in delegateable shares is the difference between sharesAfter and sharesBefore +└── when recordBeaconChainETHBalanceUpdate is called + ├── given that the podOwner's eigenPod is not the caller + │ └── it should revert + ├── given that the podOwner is a zero address + │ └── it should revert + ├── given that sharesDelta is not a whole gwei amount + │ ├── it should revert + │ └── given that the shares delta is valid + │ └── it should update the podOwnerShares + ├── given that the change in delegateable shares is positive *** + │ └── it should increase delegated shares on the delegationManager + ├── given that the change in delegateable shares is negative *** + │ └── it should decrease delegated shares on the delegationManager + ├── given that the change in delegateable shares is 0 *** + │ └── it should only update the podOwnerShares + └── given that the function is reentered *** + └── it should revert \ No newline at end of file diff --git a/src/test/tree/EigenPodManagerUnit.tree b/src/test/tree/EigenPodManagerUnit.tree index f0803b327..c8807c2be 100644 --- a/src/test/tree/EigenPodManagerUnit.tree +++ b/src/test/tree/EigenPodManagerUnit.tree @@ -1,4 +1,4 @@ -├── EigenPodManager Tree (*** denotes that integrationt tests are needed to validate path) +├── EigenPodManager Tree (*** denotes that integration tests are needed to validate path) ├── when contract is deployed and initialized │ └── it should properly set storage ├── when initialize called again @@ -23,7 +23,7 @@ ├── when updateBeaconChainOracle is called │ ├── given the user is not the owner │ │ └── it should revert -│ └── given the user is the owner +│ └── given the user is the owner │ └── it should set the beacon chain oracle ├── when addShares is called │ ├── given that the caller is not the delegationManager diff --git a/src/test/tree/StrategyManangerUnit.tree b/src/test/tree/StrategyManangerUnit.tree new file mode 100644 index 000000000..62340c8b8 --- /dev/null +++ b/src/test/tree/StrategyManangerUnit.tree @@ -0,0 +1,111 @@ +├── StrategyManagerUnit.t.sol (*** denotes that integration tests are needed to validate path) +├── initialize +| ├── given that initialized is only called once +│ │ └── it should set the storage variables correctly (owner, strategyWhitelister, pauserRegistry) +│ └── given that initialize is called again +│ └── it should revert +├── depositIntoStrategy() +│ ├── given that deposits paused +│ │ └── it should revert +│ ├── given the function is re-entered +│ │ └── it should revert +│ ├── given that the strategy is not whitelisted +│ │ └── it should revert +│ ├── given the token safeTransferFrom() reverts +│ │ └── it should revert +│ └── given that token safeTransferFrom() succeeds +│ ├── given the staker has existing shares in strategy (not first deposit) +│ │ └── it should increase shares, nonce. while stakerStrategyListLength is unchanged +│ ├── given the staker has no existing shares in strategy (first deposit) +│ │ └── stakerStrategyListLength increases by 1 and shares increase +│ ├── given the staker has delegated to a operator *** +│ │ └── it should deposit successfully with shares increase, including delegated shares +│ └── given the staker is not delegated +│ └── it should deposit successfully with shares increase +├── depositIntoStrategyWithSignature() +│ ├── given that deposits paused +│ │ └── it should revert +│ ├── given the function is re-entered +│ │ └── it should revert +│ ├── given the signature expired +│ │ └── it should revert +│ ├── given that deposits paused and strategy not whitelisted +│ │ └── it should revert +│ ├── given the staker is a EOA +│ │ ├── given the signature verification fails +│ │ │ └── it should revert +│ │ └── given the signature verification succeeds +│ │ ├── given the token safeTransferFrom reverts +│ │ │ └── it should revert +│ │ └── given the token safeTransferFrom succeeds +│ │ ├── given that the staker has delegated to a operator *** +│ │ │ └── it should deposit successfully with shares and nonce increase, including delegated shares +│ │ └── given that the staker is not delegated +│ │ └── it should deposit successfully with shares and nonce increase +│ └── given the staker is a contract +│ ├── given the contract isn't EIP1271 compliant +│ │ └── it should revert +│ ├── given the signature verification fails, isValidSignature() return != EIP1271_MAGICVALUE +│ │ └── it should revert +│ └── given the signature verification succeeds, isValidSignature() returns EIP1271_MAGICVALUE +│ ├── given the token safeTransferFrom reverts +│ │ └── it should revert +│ └── given the token safeTransferFrom succeeds +│ ├── given the staker has delegated to a operator *** +│ │ └── it should deposit successfully with shares and nonce increase, including delegated shares +│ └── given the staker is not delegated +│ └── it should deposit successfully with shares and nonce increase +├── removeShares() +│ ├── given not called by DelegationManager +│ │ └── it should revert +│ ├── given the share amount is 0 +│ │ └── it should revert +│ ├── given the share amount is too high, higher than deposited amount +│ │ └── it should revert +│ ├── given the share amount is equal to the deposited amount +│ │ └── staker shares should be 0 with decremented stakerStrategyListLength +│ └── given the share amount is less than the deposited amount +│ └── staker shares should now be deposited - shares amount, unchanged stakerStrategyListLength +├── addShares() +│ ├── given not called by DelegationManager +│ │ └── it should revert +│ ├── given the share amount is 0 +│ │ └── it should revert +│ ├── given the staker is 0 address +│ │ └── it should revert +│ ├── given adding shares with 0 existing shares +│ │ └── it should increase shares and increment stakerStrategyListLength +│ ├── given adding shares with 0 existing shares and staker has MAX_STAKER_STRATEGY_LIST_LENGTH +│ │ └── it should revert +│ └── given the adding shares with > 0 existing shares +│ └── it should increase shares, unchanged stakerStrategyListLength +├── withdrawSharesAsTokens() +│ ├── given not called by DelegationManager +│ │ └── it should revert +│ └── given that deposited strategy is called +│ │ └── it should withdraw tokens from strategy with token balanceOf() update +├── setStrategyWhitelister() +│ ├── given not called by owner +│ │ └── it should revert +│ └── given called by owner address +│ └── it should update strategyWhitelister address +├── addStrategiesToDepositWhitelist() +│ ├── given not called by strategyWhitelister address +│ │ └── it should revert +│ └── given the strategyWhitelister address is called +│ ├── given adding one single strategy that is already whitelisted +│ │ └── it should not emit StrategyAddedToDepositWhitelist with mapping still true +│ ├── given adding one single strategy +│ │ └── it should whitelist the new strategy with mapping set to true +│ └── given adding multiple strategies to whitelist +│ └── it should whitelist all new strategies with mappings set to true +└── removeStrategiesFromDepositWhitelist() + ├── given not called by strategyWhitelister address + │ └── it should revert + └── given called by strategyWhitelister address + ├── given removing one single strategy that is not whitelisted + │ └── it shouldn't emit StrategyRemovedFromDepositWhitelist with mapping still false + ├── given removing one single strategy + │ └── it should de-whitelist the new strategy with mapping set to false + └── given removing multiple strategies to whitelist + └── it should de-whitelist all specified strategies with mappings set to false \ No newline at end of file diff --git a/src/test/unit/EigenPodManagerUnit.t.sol b/src/test/unit/EigenPodManagerUnit.t.sol index d45d03348..a4cdcf1e3 100644 --- a/src/test/unit/EigenPodManagerUnit.t.sol +++ b/src/test/unit/EigenPodManagerUnit.t.sol @@ -6,14 +6,10 @@ import "forge-std/Test.sol"; import "../../contracts/pods/EigenPodManager.sol"; import "../../contracts/pods/EigenPodPausingConstants.sol"; -import "../../contracts/permissions/PauserRegistry.sol"; import "../events/IEigenPodManagerEvents.sol"; import "../utils/EigenLayerUnitTestSetup.sol"; import "../harnesses/EigenPodManagerWrapper.sol"; -import "../mocks/DelegationManagerMock.sol"; -import "../mocks/SlasherMock.sol"; -import "../mocks/StrategyManagerMock.sol"; import "../mocks/EigenPodMock.sol"; import "../mocks/ETHDepositMock.sol"; @@ -24,14 +20,7 @@ contract EigenPodManagerUnitTests is EigenLayerUnitTestSetup { using stdStorage for StdStorage; - // Proxy Admin & Pauser Registry - ProxyAdmin public proxyAdmin; - PauserRegistry public pauserRegistry; - // Mocks - StrategyManagerMock public strategyManagerMock; - DelegationManagerMock public delegationManagerMock; - SlasherMock public slasherMock; IETHPOSDeposit public ethPOSMock; IEigenPod public eigenPodMockImplementation; IBeacon public eigenPodBeacon; // Proxy for eigenPodMockImplementation @@ -43,19 +32,10 @@ contract EigenPodManagerUnitTests is EigenLayerUnitTestSetup { IEigenPod public defaultPod; address public initialOwner = address(this); - function setUp() virtual public { - // Deploy ProxyAdmin - proxyAdmin = new ProxyAdmin(); - - // Initialize PauserRegistry - address[] memory pausers = new address[](1); - pausers[0] = pauser; - pauserRegistry = new PauserRegistry(pausers, unpauser); + function setUp() virtual override public { + EigenLayerUnitTestSetup.setUp(); // Deploy Mocks - slasherMock = new SlasherMock(); - delegationManagerMock = new DelegationManagerMock(); - strategyManagerMock = new StrategyManagerMock(); ethPOSMock = new ETHPOSDepositMock(); eigenPodMockImplementation = new EigenPodMock(); eigenPodBeacon = new UpgradeableBeacon(address(eigenPodMockImplementation)); @@ -72,7 +52,7 @@ contract EigenPodManagerUnitTests is EigenLayerUnitTestSetup { address( new TransparentUpgradeableProxy( address(eigenPodManagerImplementation), - address(proxyAdmin), + address(eigenLayerProxyAdmin), abi.encodeWithSelector( EigenPodManager.initialize.selector, type(uint256).max /*maxPods*/, @@ -91,9 +71,8 @@ contract EigenPodManagerUnitTests is EigenLayerUnitTestSetup { // Set defaultPod defaultPod = eigenPodManager.getPod(defaultStaker); - // Exclude the zero address, the proxyAdmin and the eigenPodManager itself from fuzzed inputs + // Exclude the zero address, and the eigenPodManager itself from fuzzed inputs addressIsExcludedFromFuzzedInputs[address(0)] = true; - addressIsExcludedFromFuzzedInputs[address(proxyAdmin)] = true; addressIsExcludedFromFuzzedInputs[address(eigenPodManager)] = true; } @@ -541,7 +520,7 @@ contract EigenPodManagerUnitTests_ShareAdjustmentCalculationTests is EigenPodMan slasherMock, delegationManagerMock ); - proxyAdmin.upgrade(TransparentUpgradeableProxy(payable(address(eigenPodManager))), address(eigenPodManagerWrapper)); + eigenLayerProxyAdmin.upgrade(TransparentUpgradeableProxy(payable(address(eigenPodManager))), address(eigenPodManagerWrapper)); } function testFuzz_shareAdjustment_negativeToNegative(int256 sharesBefore, int256 sharesAfter) public { @@ -575,4 +554,4 @@ contract EigenPodManagerUnitTests_ShareAdjustmentCalculationTests is EigenPodMan int256 sharesDelta = eigenPodManagerWrapper.calculateChangeInDelegatableShares(sharesBefore, sharesAfter); assertEq(sharesDelta, sharesAfter - sharesBefore, "Shares delta must be equal to the difference between sharesAfter and sharesBefore"); } -} \ No newline at end of file +} diff --git a/src/test/unit/SlasherUnit.t.sol b/src/test/unit/SlasherUnit.t.sol index 9f3b91067..897bac988 100644 --- a/src/test/unit/SlasherUnit.t.sol +++ b/src/test/unit/SlasherUnit.t.sol @@ -18,7 +18,7 @@ import "../mocks/Reverter.sol"; import "../mocks/ERC20Mock.sol"; -import "./Utils.sol"; +import "src/test/utils/Utils.sol"; contract SlasherUnitTests is Test, Utils { diff --git a/src/test/unit/StrategyManagerUnit.t.sol b/src/test/unit/StrategyManagerUnit.t.sol index 93acdbd85..94be5dee4 100644 --- a/src/test/unit/StrategyManagerUnit.t.sol +++ b/src/test/unit/StrategyManagerUnit.t.sol @@ -1,69 +1,37 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity =0.8.12; -import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import "@openzeppelin/contracts/mocks/ERC1271WalletMock.sol"; - -import "forge-std/Test.sol"; - -import "../../contracts/core/StrategyManager.sol"; -import "../../contracts/strategies/StrategyBase.sol"; -import "../../contracts/permissions/PauserRegistry.sol"; -import "../mocks/DelegationManagerMock.sol"; -import "../mocks/SlasherMock.sol"; -import "../mocks/EigenPodManagerMock.sol"; -import "../mocks/Reenterer.sol"; -import "../mocks/Reverter.sol"; - -import "../mocks/ERC20Mock.sol"; - -import "./Utils.sol"; - -contract StrategyManagerUnitTests is Test, Utils { - Vm cheats = Vm(HEVM_ADDRESS); - - uint256 public REQUIRED_BALANCE_WEI = 32 ether; - - ProxyAdmin public proxyAdmin; - PauserRegistry public pauserRegistry; - +import "src/contracts/core/StrategyManager.sol"; +import "src/contracts/strategies/StrategyBase.sol"; +import "src/contracts/permissions/PauserRegistry.sol"; +import "src/test/mocks/ERC20Mock.sol"; +import "src/test/mocks/ERC20_SetTransferReverting_Mock.sol"; +import "src/test/mocks/Reverter.sol"; +import "src/test/mocks/Reenterer.sol"; +import "src/test/utils/EigenLayerUnitTestSetup.sol"; + +/** + * @notice Unit testing of the StrategyMananger contract, entire withdrawal tests related to the + * DelegationManager are not tested here but callable functions by the DelegationManager are mocked and tested here. + * Contracts tested: StrategyManager.sol + * Contracts not mocked: StrategyBase, PauserRegistry + */ +contract StrategyManagerUnitTests is EigenLayerUnitTestSetup { StrategyManager public strategyManagerImplementation; StrategyManager public strategyManager; - DelegationManagerMock public delegationManagerMock; - SlasherMock public slasherMock; - EigenPodManagerMock public eigenPodManagerMock; + IERC20 public dummyToken; + ERC20_SetTransferReverting_Mock public revertToken; StrategyBase public dummyStrat; StrategyBase public dummyStrat2; StrategyBase public dummyStrat3; - IStrategy public beaconChainETHStrategy; - - IERC20 public dummyToken; - Reenterer public reenterer; - uint256 GWEI_TO_WEI = 1e9; - - address public pauser = address(555); - address public unpauser = address(999); - address initialOwner = address(this); - - uint256[] public emptyUintArray; - - // used as transient storage to fix stack-too-deep errors - IStrategy public _tempStrategyStorage; - address public _tempStakerStorage; uint256 public privateKey = 111111; - - mapping(address => bool) public addressIsExcludedFromFuzzedInputs; - - modifier filterFuzzedAddressInputs(address fuzzedAddress) { - cheats.assume(!addressIsExcludedFromFuzzedInputs[fuzzedAddress]); - _; - } + address constant dummyAdmin = address(uint160(uint256(keccak256("DummyAdmin")))); /** * @notice Emitted when a new deposit occurs on behalf of `depositor`. @@ -119,22 +87,14 @@ contract StrategyManagerUnitTests is Test, Utils { /// @notice Emitted when the `withdrawalDelayBlocks` variable is modified from `previousValue` to `newValue`. event WithdrawalDelayBlocksSet(uint256 previousValue, uint256 newValue); - function setUp() public virtual { - proxyAdmin = new ProxyAdmin(); - - address[] memory pausers = new address[](1); - pausers[0] = pauser; - pauserRegistry = new PauserRegistry(pausers, unpauser); - - slasherMock = new SlasherMock(); - delegationManagerMock = new DelegationManagerMock(); - eigenPodManagerMock = new EigenPodManagerMock(); + function setUp() public override { + EigenLayerUnitTestSetup.setUp(); strategyManagerImplementation = new StrategyManager(delegationManagerMock, eigenPodManagerMock, slasherMock); strategyManager = StrategyManager( address( new TransparentUpgradeableProxy( address(strategyManagerImplementation), - address(proxyAdmin), + address(eigenLayerProxyAdmin), abi.encodeWithSelector( StrategyManager.initialize.selector, initialOwner, @@ -146,37 +106,210 @@ contract StrategyManagerUnitTests is Test, Utils { ) ); dummyToken = new ERC20Mock(); - dummyStrat = deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin); - dummyStrat2 = deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin); - dummyStrat3 = deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin); + revertToken = new ERC20_SetTransferReverting_Mock(1000e18, address(this)); + revertToken.setTransfersRevert(true); + dummyStrat = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin); + dummyStrat2 = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin); + dummyStrat3 = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin); // whitelist the strategy for deposit - cheats.startPrank(strategyManager.owner()); - IStrategy[] memory _strategy = new IStrategy[](3); - _strategy[0] = dummyStrat; - _strategy[1] = dummyStrat2; - _strategy[2] = dummyStrat3; - for (uint256 i = 0; i < _strategy.length; ++i) { + cheats.prank(strategyManager.owner()); + IStrategy[] memory _strategies = new IStrategy[](3); + _strategies[0] = dummyStrat; + _strategies[1] = dummyStrat2; + _strategies[2] = dummyStrat3; + for (uint256 i = 0; i < _strategies.length; ++i) { cheats.expectEmit(true, true, true, true, address(strategyManager)); - emit StrategyAddedToDepositWhitelist(_strategy[i]); + emit StrategyAddedToDepositWhitelist(_strategies[i]); } - strategyManager.addStrategiesToDepositWhitelist(_strategy); - cheats.stopPrank(); + strategyManager.addStrategiesToDepositWhitelist(_strategies); + + addressIsExcludedFromFuzzedInputs[address(reenterer)] = true; + } + + // INTERNAL / HELPER FUNCTIONS + function _deployNewStrategy( + IERC20 _token, + IStrategyManager _strategyManager, + IPauserRegistry _pauserRegistry, + address admin + ) public returns (StrategyBase) { + StrategyBase newStrategy = new StrategyBase(_strategyManager); + newStrategy = StrategyBase(address(new TransparentUpgradeableProxy(address(newStrategy), address(admin), ""))); + newStrategy.initialize(_token, _pauserRegistry); + return newStrategy; + } + + function _depositIntoStrategySuccessfully( + IStrategy strategy, + address staker, + uint256 amount + ) internal filterFuzzedAddressInputs(staker) { + IERC20 token = dummyToken; + + // filter out zero case since it will revert with "StrategyManager._addShares: shares should not be zero!" + cheats.assume(amount != 0); + // filter out zero address because the mock ERC20 we are using will revert on using it + cheats.assume(staker != address(0)); + // sanity check / filter + cheats.assume(amount <= token.balanceOf(address(this))); + + uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategy); + uint256 stakerStrategyListLengthBefore = strategyManager.stakerStrategyListLength(staker); + + // needed for expecting an event with the right parameters + uint256 expectedShares = amount; + + cheats.prank(staker); + cheats.expectEmit(true, true, true, true, address(strategyManager)); + emit Deposit(staker, token, strategy, expectedShares); + uint256 shares = strategyManager.depositIntoStrategy(strategy, token, amount); + + uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategy); + uint256 stakerStrategyListLengthAfter = strategyManager.stakerStrategyListLength(staker); + + assertEq(sharesAfter, sharesBefore + shares, "sharesAfter != sharesBefore + shares"); + if (sharesBefore == 0) { + assertEq( + stakerStrategyListLengthAfter, + stakerStrategyListLengthBefore + 1, + "stakerStrategyListLengthAfter != stakerStrategyListLengthBefore + 1" + ); + assertEq( + address(strategyManager.stakerStrategyList(staker, stakerStrategyListLengthAfter - 1)), + address(strategy), + "strategyManager.stakerStrategyList(staker, stakerStrategyListLengthAfter - 1) != strategy" + ); + } + } + + // internal function for de-duping code. expects success if `expectedRevertMessage` is empty and expiry is valid. + function _depositIntoStrategyWithSignature( + address staker, + uint256 amount, + uint256 expiry, + string memory expectedRevertMessage + ) internal returns (bytes memory) { + // filter out zero case since it will revert with "StrategyManager._addShares: shares should not be zero!" + cheats.assume(amount != 0); + // sanity check / filter + cheats.assume(amount <= dummyToken.balanceOf(address(this))); + + uint256 nonceBefore = strategyManager.nonces(staker); + bytes memory signature; + + { + bytes32 structHash = keccak256( + abi.encode(strategyManager.DEPOSIT_TYPEHASH(), dummyStrat, dummyToken, amount, nonceBefore, expiry) + ); + bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.domainSeparator(), structHash)); + + (uint8 v, bytes32 r, bytes32 s) = cheats.sign(privateKey, digestHash); + + signature = abi.encodePacked(r, s, v); + } + + uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, dummyStrat); + + bool expectedRevertMessageIsempty; + { + string memory emptyString; + expectedRevertMessageIsempty = + keccak256(abi.encodePacked(expectedRevertMessage)) == keccak256(abi.encodePacked(emptyString)); + } + if (!expectedRevertMessageIsempty) { + cheats.expectRevert(bytes(expectedRevertMessage)); + } else if (expiry < block.timestamp) { + cheats.expectRevert("StrategyManager.depositIntoStrategyWithSignature: signature expired"); + } else { + // needed for expecting an event with the right parameters + uint256 expectedShares = amount; + cheats.expectEmit(true, true, true, true, address(strategyManager)); + emit Deposit(staker, dummyToken, dummyStrat, expectedShares); + } + uint256 shares = strategyManager.depositIntoStrategyWithSignature( + dummyStrat, + dummyToken, + amount, + staker, + expiry, + signature + ); + + uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, dummyStrat); + uint256 nonceAfter = strategyManager.nonces(staker); + + if (expiry >= block.timestamp && expectedRevertMessageIsempty) { + assertEq(sharesAfter, sharesBefore + shares, "sharesAfter != sharesBefore + shares"); + assertEq(nonceAfter, nonceBefore + 1, "nonceAfter != nonceBefore + 1"); + } + return signature; + } - beaconChainETHStrategy = eigenPodManagerMock.beaconChainETHStrategy(); + /** + * @notice internal function to help check if a strategy is part of list of deposited strategies for a staker + * Used to check if removed correctly after withdrawing all shares for a given strategy + */ + function _isDepositedStrategy(address staker, IStrategy strategy) internal view returns (bool) { + uint256 stakerStrategyListLength = strategyManager.stakerStrategyListLength(staker); + for (uint256 i = 0; i < stakerStrategyListLength; ++i) { + if (strategyManager.stakerStrategyList(staker, i) == strategy) { + return true; + } + } + return false; + } + + /** + * @notice Deploys numberOfStrategiesToAdd new strategies and adds them to the whitelist + */ + function _addStrategiesToWhitelist(uint8 numberOfStrategiesToAdd) internal returns (IStrategy[] memory) { + IStrategy[] memory strategyArray = new IStrategy[](numberOfStrategiesToAdd); + // loop that deploys a new strategy and adds it to the array + for (uint256 i = 0; i < numberOfStrategiesToAdd; ++i) { + IStrategy _strategy = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin); + strategyArray[i] = _strategy; + assertFalse(strategyManager.strategyIsWhitelistedForDeposit(_strategy), "strategy improperly whitelisted?"); + } + + cheats.prank(strategyManager.strategyWhitelister()); + for (uint256 i = 0; i < numberOfStrategiesToAdd; ++i) { + cheats.expectEmit(true, true, true, true, address(strategyManager)); + emit StrategyAddedToDepositWhitelist(strategyArray[i]); + } + strategyManager.addStrategiesToDepositWhitelist(strategyArray); + + for (uint256 i = 0; i < numberOfStrategiesToAdd; ++i) { + assertTrue(strategyManager.strategyIsWhitelistedForDeposit(strategyArray[i]), "strategy not whitelisted"); + } - // excude the zero address, the proxyAdmin and the eigenPodManagerMock from fuzzed inputs - addressIsExcludedFromFuzzedInputs[address(0)] = true; - addressIsExcludedFromFuzzedInputs[address(proxyAdmin)] = true; - addressIsExcludedFromFuzzedInputs[address(eigenPodManagerMock)] = true; + return strategyArray; } +} - function testCannotReinitialize() public { - cheats.expectRevert(bytes("Initializable: contract is already initialized")); +contract StrategyManagerUnitTests_initialize is StrategyManagerUnitTests { + function test_CannotReinitialize() public { + cheats.expectRevert("Initializable: contract is already initialized"); strategyManager.initialize(initialOwner, initialOwner, pauserRegistry, 0); } - function testDepositIntoStrategySuccessfully( + function test_InitializedStorageProperly() public { + assertEq(strategyManager.owner(), initialOwner, "strategyManager.owner() != initialOwner"); + assertEq( + strategyManager.strategyWhitelister(), + initialOwner, + "strategyManager.strategyWhitelister() != initialOwner" + ); + assertEq( + address(strategyManager.pauserRegistry()), + address(pauserRegistry), + "strategyManager.pauserRegistry() != pauserRegistry" + ); + } +} + +contract StrategyManagerUnitTests_depositIntoStrategy is StrategyManagerUnitTests { + function testFuzz_depositIntoStrategySuccessfully( address staker, uint256 amount ) public filterFuzzedAddressInputs(staker) { @@ -199,48 +332,54 @@ contract StrategyManagerUnitTests is Test, Utils { // needed for expecting an event with the right parameters uint256 expectedShares = strategy.underlyingToShares(amount); - cheats.startPrank(staker); + cheats.prank(staker); cheats.expectEmit(true, true, true, true, address(strategyManager)); emit Deposit(staker, token, strategy, expectedShares); uint256 shares = strategyManager.depositIntoStrategy(strategy, token, amount); - cheats.stopPrank(); uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategy); uint256 stakerStrategyListLengthAfter = strategyManager.stakerStrategyListLength(staker); - require(sharesAfter == sharesBefore + shares, "sharesAfter != sharesBefore + shares"); + assertEq(sharesAfter, sharesBefore + shares, "sharesAfter != sharesBefore + shares"); if (sharesBefore == 0) { - require( - stakerStrategyListLengthAfter == stakerStrategyListLengthBefore + 1, + assertEq( + stakerStrategyListLengthAfter, + stakerStrategyListLengthBefore + 1, "stakerStrategyListLengthAfter != stakerStrategyListLengthBefore + 1" ); - require( - strategyManager.stakerStrategyList(staker, stakerStrategyListLengthAfter - 1) == strategy, + assertEq( + address(strategyManager.stakerStrategyList(staker, stakerStrategyListLengthAfter - 1)), + address(strategy), "strategyManager.stakerStrategyList(staker, stakerStrategyListLengthAfter - 1) != strategy" ); + } else { + assertEq( + stakerStrategyListLengthAfter, + stakerStrategyListLengthBefore, + "stakerStrategyListLengthAfter != stakerStrategyListLengthBefore" + ); } } - function testDepositIntoStrategySuccessfullyTwice() public { + function test_DepositWhenStrategySharesExist() public { address staker = address(this); uint256 amount = 1e18; - testDepositIntoStrategySuccessfully(staker, amount); - testDepositIntoStrategySuccessfully(staker, amount); + testFuzz_depositIntoStrategySuccessfully(staker, amount); + testFuzz_depositIntoStrategySuccessfully(staker, amount); } - function testDepositIntoStrategyRevertsWhenDepositsPaused() public { + function test_Revert_WhenDepositsPaused() public { uint256 amount = 1e18; // pause deposits - cheats.startPrank(pauser); + cheats.prank(pauser); strategyManager.pause(1); - cheats.stopPrank(); - cheats.expectRevert(bytes("Pausable: index is paused")); + cheats.expectRevert("Pausable: index is paused"); strategyManager.depositIntoStrategy(dummyStrat, dummyToken, amount); } - function testDepositIntoStrategyRevertsWhenReentering() public { + function test_Revert_WhenReentering() public { uint256 amount = 1e18; reenterer = new Reenterer(); @@ -271,77 +410,216 @@ contract StrategyManagerUnitTests is Test, Utils { strategyManager.depositIntoStrategy(IStrategy(address(reenterer)), dummyToken, amount); } - function testDepositIntoStrategyWithSignatureSuccessfully(uint256 amount, uint256 expiry) public { - // min shares must be minted on strategy - cheats.assume(amount >= 1); - - address staker = cheats.addr(privateKey); - // not expecting a revert, so input an empty string - string memory expectedRevertMessage; - _depositIntoStrategyWithSignature(staker, amount, expiry, expectedRevertMessage); - } + function test_Revert_WhenTokenSafeTransferFromReverts() external { + // replace 'dummyStrat' with one that uses a reverting token + dummyToken = IERC20(address(new Reverter())); + dummyStrat = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin); - function testDepositIntoStrategyWithSignatureReplay(uint256 amount, uint256 expiry) public { - // min shares must be minted on strategy - cheats.assume(amount >= 1); - cheats.assume(expiry > block.timestamp); + // whitelist the strategy for deposit + cheats.startPrank(strategyManager.owner()); + IStrategy[] memory _strategy = new IStrategy[](1); + _strategy[0] = dummyStrat; + strategyManager.addStrategiesToDepositWhitelist(_strategy); + cheats.stopPrank(); - address staker = cheats.addr(privateKey); - // not expecting a revert, so input an empty string - bytes memory signature = _depositIntoStrategyWithSignature(staker, amount, expiry, ""); + address staker = address(this); + IERC20 token = dummyToken; + uint256 amount = 1e18; + IStrategy strategy = dummyStrat; - cheats.expectRevert(bytes("EIP1271SignatureUtils.checkSignature_EIP1271: signature not from signer")); - strategyManager.depositIntoStrategyWithSignature(dummyStrat, dummyToken, amount, staker, expiry, signature); + cheats.prank(staker); + cheats.expectRevert("Reverter: I am a contract that always reverts"); + strategyManager.depositIntoStrategy(strategy, token, amount); } - // tries depositing using a signature and an EIP 1271 compliant wallet - function testDepositIntoStrategyWithSignature_WithContractWallet_Successfully( - uint256 amount, - uint256 expiry - ) public { - // min shares must be minted on strategy - cheats.assume(amount >= 1); - - address staker = cheats.addr(privateKey); + function test_Revert_WhenTokenDoesNotExist() external { + // replace 'dummyStrat' with one that uses a non-existent token + dummyToken = IERC20(address(5678)); + dummyStrat = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin); - // deploy ERC1271WalletMock for staker to use - cheats.startPrank(staker); - ERC1271WalletMock wallet = new ERC1271WalletMock(staker); + // whitelist the strategy for deposit + cheats.startPrank(strategyManager.owner()); + IStrategy[] memory _strategy = new IStrategy[](1); + _strategy[0] = dummyStrat; + strategyManager.addStrategiesToDepositWhitelist(_strategy); cheats.stopPrank(); - staker = address(wallet); - // not expecting a revert, so input an empty string - string memory expectedRevertMessage; - _depositIntoStrategyWithSignature(staker, amount, expiry, expectedRevertMessage); - } + address staker = address(this); + IERC20 token = dummyToken; + uint256 amount = 1e18; + IStrategy strategy = dummyStrat; - // tries depositing using a signature and an EIP 1271 compliant wallet, *but* providing a bad signature - function testDepositIntoStrategyWithSignature_WithContractWallet_BadSignature(uint256 amount) public { - // min shares must be minted on strategy - cheats.assume(amount >= 1); + cheats.prank(staker); + cheats.expectRevert("Address: call to non-contract"); + strategyManager.depositIntoStrategy(strategy, token, amount); + } - address staker = cheats.addr(privateKey); - IStrategy strategy = dummyStrat; - IERC20 token = dummyToken; + function test_Revert_WhenStrategyDepositFunctionReverts() external { + // replace 'dummyStrat' with one that always reverts + dummyStrat = StrategyBase(address(new Reverter())); - // deploy ERC1271WalletMock for staker to use - cheats.startPrank(staker); - ERC1271WalletMock wallet = new ERC1271WalletMock(staker); + // whitelist the strategy for deposit + cheats.startPrank(strategyManager.owner()); + IStrategy[] memory _strategy = new IStrategy[](1); + _strategy[0] = dummyStrat; + strategyManager.addStrategiesToDepositWhitelist(_strategy); cheats.stopPrank(); - staker = address(wallet); - // filter out zero case since it will revert with "StrategyManager._addShares: shares should not be zero!" - cheats.assume(amount != 0); - // sanity check / filter - cheats.assume(amount <= token.balanceOf(address(this))); + address staker = address(this); + IERC20 token = dummyToken; + uint256 amount = 1e18; + IStrategy strategy = dummyStrat; - uint256 nonceBefore = strategyManager.nonces(staker); - uint256 expiry = type(uint256).max; - bytes memory signature; + cheats.prank(staker); + cheats.expectRevert("Reverter: I am a contract that always reverts"); + strategyManager.depositIntoStrategy(strategy, token, amount); + } - { - bytes32 structHash = keccak256( - abi.encode(strategyManager.DEPOSIT_TYPEHASH(), strategy, token, amount, nonceBefore, expiry) + function test_Revert_WhenStrategyDoesNotExist() external { + // replace 'dummyStrat' with one that does not exist + dummyStrat = StrategyBase(address(5678)); + + // whitelist the strategy for deposit + cheats.startPrank(strategyManager.owner()); + IStrategy[] memory _strategy = new IStrategy[](1); + _strategy[0] = dummyStrat; + strategyManager.addStrategiesToDepositWhitelist(_strategy); + cheats.stopPrank(); + + address staker = address(this); + IERC20 token = dummyToken; + uint256 amount = 1e18; + IStrategy strategy = dummyStrat; + + cheats.prank(staker); + cheats.expectRevert(); + strategyManager.depositIntoStrategy(strategy, token, amount); + } + + function test_Revert_WhenStrategyNotWhitelisted() external { + // replace 'dummyStrat' with one that is not whitelisted + dummyStrat = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin); + + address staker = address(this); + IERC20 token = dummyToken; + uint256 amount = 1e18; + IStrategy strategy = dummyStrat; + + cheats.prank(staker); + cheats.expectRevert("StrategyManager.onlyStrategiesWhitelistedForDeposit: strategy not whitelisted"); + strategyManager.depositIntoStrategy(strategy, token, amount); + } + + function test_addShares_Revert_WhenSharesIsZero() external { + // replace dummyStrat with Reenterer contract + reenterer = new Reenterer(); + dummyStrat = StrategyBase(address(reenterer)); + + // whitelist the strategy for deposit + cheats.startPrank(strategyManager.owner()); + IStrategy[] memory _strategy = new IStrategy[](1); + _strategy[0] = dummyStrat; + strategyManager.addStrategiesToDepositWhitelist(_strategy); + cheats.stopPrank(); + + address staker = address(this); + IStrategy strategy = dummyStrat; + IERC20 token = dummyToken; + uint256 amount = 1e18; + + reenterer.prepareReturnData(abi.encode(uint256(0))); + + cheats.prank(staker); + cheats.expectRevert("StrategyManager._addShares: shares should not be zero!"); + strategyManager.depositIntoStrategy(strategy, token, amount); + } +} + +contract StrategyManagerUnitTests_depositIntoStrategyWithSignature is StrategyManagerUnitTests { + function test_Revert_WhenSignatureInvalid() public { + address staker = cheats.addr(privateKey); + IStrategy strategy = dummyStrat; + IERC20 token = dummyToken; + uint256 amount = 1e18; + + uint256 nonceBefore = strategyManager.nonces(staker); + uint256 expiry = block.timestamp; + bytes memory signature; + + { + bytes32 structHash = keccak256( + abi.encode(strategyManager.DEPOSIT_TYPEHASH(), strategy, token, amount, nonceBefore, expiry) + ); + bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.domainSeparator(), structHash)); + + (uint8 v, bytes32 r, bytes32 s) = cheats.sign(privateKey, digestHash); + + signature = abi.encodePacked(r, s, v); + } + + uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategy); + + cheats.expectRevert("EIP1271SignatureUtils.checkSignature_EIP1271: signature not from signer"); + // call with `notStaker` as input instead of `staker` address + address notStaker = address(3333); + strategyManager.depositIntoStrategyWithSignature(strategy, token, amount, notStaker, expiry, signature); + + uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategy); + uint256 nonceAfter = strategyManager.nonces(staker); + + assertEq(sharesAfter, sharesBefore, "sharesAfter != sharesBefore"); + assertEq(nonceAfter, nonceBefore, "nonceAfter != nonceBefore"); + } + + function testFuzz_DepositSuccessfully(uint256 amount, uint256 expiry) public { + // min shares must be minted on strategy + cheats.assume(amount >= 1); + + address staker = cheats.addr(privateKey); + // not expecting a revert, so input an empty string + string memory expectedRevertMessage; + _depositIntoStrategyWithSignature(staker, amount, expiry, expectedRevertMessage); + } + + function testFuzz_Revert_SignatureReplay(uint256 amount, uint256 expiry) public { + // min shares must be minted on strategy + cheats.assume(amount >= 1); + cheats.assume(expiry > block.timestamp); + + address staker = cheats.addr(privateKey); + // not expecting a revert, so input an empty string + bytes memory signature = _depositIntoStrategyWithSignature(staker, amount, expiry, ""); + + cheats.expectRevert("EIP1271SignatureUtils.checkSignature_EIP1271: signature not from signer"); + strategyManager.depositIntoStrategyWithSignature(dummyStrat, dummyToken, amount, staker, expiry, signature); + } + + // tries depositing using a signature and an EIP 1271 compliant wallet, *but* providing a bad signature + function testFuzz_Revert_WithContractWallet_BadSignature(uint256 amount) public { + // min shares must be minted on strategy + cheats.assume(amount >= 1); + + address staker = cheats.addr(privateKey); + IStrategy strategy = dummyStrat; + IERC20 token = dummyToken; + + // deploy ERC1271WalletMock for staker to use + cheats.prank(staker); + ERC1271WalletMock wallet = new ERC1271WalletMock(staker); + staker = address(wallet); + + // filter out zero case since it will revert with "StrategyManager._addShares: shares should not be zero!" + cheats.assume(amount != 0); + // sanity check / filter + cheats.assume(amount <= token.balanceOf(address(this))); + + uint256 nonceBefore = strategyManager.nonces(staker); + uint256 expiry = type(uint256).max; + bytes memory signature; + + { + bytes32 structHash = keccak256( + abi.encode(strategyManager.DEPOSIT_TYPEHASH(), strategy, token, amount, nonceBefore, expiry) ); bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.domainSeparator(), structHash)); @@ -352,14 +630,12 @@ contract StrategyManagerUnitTests is Test, Utils { signature = abi.encodePacked(r, s, v); } - cheats.expectRevert( - bytes("EIP1271SignatureUtils.checkSignature_EIP1271: ERC1271 signature verification failed") - ); + cheats.expectRevert("EIP1271SignatureUtils.checkSignature_EIP1271: ERC1271 signature verification failed"); strategyManager.depositIntoStrategyWithSignature(strategy, token, amount, staker, expiry, signature); } // tries depositing using a wallet that does not comply with EIP 1271 - function testDepositIntoStrategyWithSignature_WithContractWallet_NonconformingWallet( + function testFuzz_Revert_WithContractWallet_NonconformingWallet( uint256 amount, uint8 v, bytes32 r, @@ -373,9 +649,8 @@ contract StrategyManagerUnitTests is Test, Utils { IERC20 token = dummyToken; // deploy ERC1271WalletMock for staker to use - cheats.startPrank(staker); + cheats.prank(staker); ERC1271MaliciousMock wallet = new ERC1271MaliciousMock(); - cheats.stopPrank(); staker = address(wallet); // filter out zero case since it will revert with "StrategyManager._addShares: shares should not be zero!" @@ -390,20 +665,64 @@ contract StrategyManagerUnitTests is Test, Utils { strategyManager.depositIntoStrategyWithSignature(strategy, token, amount, staker, expiry, signature); } - function testDepositIntoStrategyWithSignatureRevertsWhenDepositsPaused() public { + // Tries depositing without token approval and transfer fails. deposit function should also revert + function test_Revert_WithContractWallet_TokenTransferFails() external { + address staker = cheats.addr(privateKey); + uint256 amount = 1e18; + uint256 nonceBefore = strategyManager.nonces(staker); + uint256 expiry = block.timestamp + 100; + bytes memory signature; + + { + bytes32 structHash = keccak256( + abi.encode(strategyManager.DEPOSIT_TYPEHASH(), dummyStrat, revertToken, amount, nonceBefore, expiry) + ); + bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.domainSeparator(), structHash)); + + (uint8 v, bytes32 r, bytes32 s) = cheats.sign(privateKey, digestHash); + + signature = abi.encodePacked(r, s, v); + } + + cheats.expectRevert("ERC20: insufficient allowance"); + strategyManager.depositIntoStrategyWithSignature(dummyStrat, revertToken, amount, staker, expiry, signature); + } + + // tries depositing using a signature and an EIP 1271 compliant wallet + function testFuzz_WithContractWallet_Successfully(uint256 amount, uint256 expiry) public { + // min shares must be minted on strategy + cheats.assume(amount >= 1); + + address staker = cheats.addr(privateKey); + + // deploy ERC1271WalletMock for staker to use + cheats.prank(staker); + ERC1271WalletMock wallet = new ERC1271WalletMock(staker); + staker = address(wallet); + + // not expecting a revert, so input an empty string + string memory expectedRevertMessage; + _depositIntoStrategyWithSignature(staker, amount, expiry, expectedRevertMessage); + } + + function test_Revert_WhenDepositsPaused() public { address staker = cheats.addr(privateKey); // pause deposits - cheats.startPrank(pauser); + cheats.prank(pauser); strategyManager.pause(1); - cheats.stopPrank(); - // not expecting a revert, so input an empty string string memory expectedRevertMessage = "Pausable: index is paused"; _depositIntoStrategyWithSignature(staker, 1e18, type(uint256).max, expectedRevertMessage); } - function testDepositIntoStrategyWithSignatureRevertsWhenReentering() public { + /** + * @notice reenterer contract which is configured as the strategy contract + * is configured to call depositIntoStrategy after reenterer.deposit() is called from the + * depositIntoStrategyWithSignature() is called from the StrategyManager. Situation is not likely to occur given + * the strategy has to be whitelisted but it at least protects from reentrant attacks + */ + function test_Revert_WhenReentering() public { reenterer = new Reenterer(); // whitelist the strategy for deposit @@ -437,8 +756,6 @@ contract StrategyManagerUnitTests is Test, Utils { signature = abi.encodePacked(r, s, v); } - uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategy); - uint256 shareAmountToReturn = amount; reenterer.prepareReturnData(abi.encode(shareAmountToReturn)); @@ -453,17 +770,10 @@ contract StrategyManagerUnitTests is Test, Utils { ); reenterer.prepare(targetToUse, msgValueToUse, calldataToUse, bytes("ReentrancyGuard: reentrant call")); } - strategyManager.depositIntoStrategyWithSignature(strategy, token, amount, staker, expiry, signature); - - uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategy); - uint256 nonceAfter = strategyManager.nonces(staker); - - require(sharesAfter == sharesBefore + shareAmountToReturn, "sharesAfter != sharesBefore + shareAmountToReturn"); - require(nonceAfter == nonceBefore + 1, "nonceAfter != nonceBefore + 1"); } - function testDepositIntoStrategyWithSignatureRevertsWhenSignatureExpired() public { + function test_Revert_WhenSignatureExpired() public { address staker = cheats.addr(privateKey); IStrategy strategy = dummyStrat; IERC20 token = dummyToken; @@ -488,451 +798,138 @@ contract StrategyManagerUnitTests is Test, Utils { uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategy); - cheats.expectRevert(bytes("StrategyManager.depositIntoStrategyWithSignature: signature expired")); + cheats.expectRevert("StrategyManager.depositIntoStrategyWithSignature: signature expired"); strategyManager.depositIntoStrategyWithSignature(strategy, token, amount, staker, expiry, signature); uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategy); uint256 nonceAfter = strategyManager.nonces(staker); - require(sharesAfter == sharesBefore, "sharesAfter != sharesBefore"); - require(nonceAfter == nonceBefore, "nonceAfter != nonceBefore"); + assertEq(sharesAfter, sharesBefore, "sharesAfter != sharesBefore"); + assertEq(nonceAfter, nonceBefore, "nonceAfter != nonceBefore"); } - function testDepositIntoStrategyWithSignatureRevertsWhenSignatureInvalid() public { + function test_Revert_WhenStrategyNotWhitelisted() external { + // replace 'dummyStrat' with one that is not whitelisted + dummyStrat = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin); + dummyToken = dummyStrat.underlyingToken(); address staker = cheats.addr(privateKey); - IStrategy strategy = dummyStrat; - IERC20 token = dummyToken; uint256 amount = 1e18; - uint256 nonceBefore = strategyManager.nonces(staker); - uint256 expiry = block.timestamp; - bytes memory signature; + string + memory expectedRevertMessage = "StrategyManager.onlyStrategiesWhitelistedForDeposit: strategy not whitelisted"; + _depositIntoStrategyWithSignature(staker, amount, type(uint256).max, expectedRevertMessage); + } +} - { - bytes32 structHash = keccak256( - abi.encode(strategyManager.DEPOSIT_TYPEHASH(), strategy, token, amount, nonceBefore, expiry) - ); - bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.domainSeparator(), structHash)); +contract StrategyManagerUnitTests_removeShares is StrategyManagerUnitTests { + /** + * @notice Should revert if not called by DelegationManager + */ + function test_Revert_DelegationManagerModifier() external { + DelegationManagerMock invalidDelegationManager = new DelegationManagerMock(); + cheats.expectRevert("StrategyManager.onlyDelegationManager: not the DelegationManager"); + invalidDelegationManager.removeShares(strategyManager, address(this), dummyStrat, 1); + } - (uint8 v, bytes32 r, bytes32 s) = cheats.sign(privateKey, digestHash); + /** + * @notice deposits a single strategy and tests removeShares() function reverts when sharesAmount is 0 + */ + function testFuzz_Revert_ZeroShares( + address staker, + uint256 depositAmount + ) external filterFuzzedAddressInputs(staker) { + cheats.assume(staker != address(0)); + cheats.assume(depositAmount > 0 && depositAmount < dummyToken.totalSupply()); + IStrategy strategy = dummyStrat; + _depositIntoStrategySuccessfully(strategy, staker, depositAmount); + cheats.expectRevert("StrategyManager._removeShares: shareAmount should not be zero!"); + delegationManagerMock.removeShares(strategyManager, staker, strategy, 0); + } - signature = abi.encodePacked(r, s, v); - } + /** + * @notice deposits a single strategy and tests removeShares() function reverts when sharesAmount is + * higher than depositAmount + */ + function testFuzz_Revert_ShareAmountTooHigh( + address staker, + uint256 depositAmount, + uint256 removeSharesAmount + ) external filterFuzzedAddressInputs(staker) { + cheats.assume(staker != address(0)); + cheats.assume(depositAmount > 0 && depositAmount < dummyToken.totalSupply()); + cheats.assume(removeSharesAmount > depositAmount); + IStrategy strategy = dummyStrat; + _depositIntoStrategySuccessfully(strategy, staker, depositAmount); + cheats.expectRevert("StrategyManager._removeShares: shareAmount too high"); + delegationManagerMock.removeShares(strategyManager, staker, strategy, removeSharesAmount); + } + /** + * @notice deposit single strategy and removeShares() for less than the deposited amount + * Shares should be updated correctly with stakerStrategyListLength unchanged + */ + function testFuzz_RemoveSharesLessThanDeposit( + address staker, + uint256 depositAmount, + uint256 removeSharesAmount + ) external filterFuzzedAddressInputs(staker) { + cheats.assume(staker != address(0)); + cheats.assume(depositAmount > 0 && depositAmount < dummyToken.totalSupply()); + cheats.assume(removeSharesAmount > 0 && removeSharesAmount < depositAmount); + IStrategy strategy = dummyStrat; + _depositIntoStrategySuccessfully(strategy, staker, depositAmount); + uint256 stakerStrategyListLengthBefore = strategyManager.stakerStrategyListLength(staker); uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategy); - - cheats.expectRevert(bytes("EIP1271SignatureUtils.checkSignature_EIP1271: signature not from signer")); - // call with `notStaker` as input instead of `staker` address - address notStaker = address(3333); - strategyManager.depositIntoStrategyWithSignature(strategy, token, amount, notStaker, expiry, signature); - + delegationManagerMock.removeShares(strategyManager, staker, strategy, removeSharesAmount); + uint256 stakerStrategyListLengthAfter = strategyManager.stakerStrategyListLength(staker); uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategy); - uint256 nonceAfter = strategyManager.nonces(staker); - - require(sharesAfter == sharesBefore, "sharesAfter != sharesBefore"); - require(nonceAfter == nonceBefore, "nonceAfter != nonceBefore"); + assertEq(sharesBefore, sharesAfter + removeSharesAmount, "Remove incorrect amount of shares"); + assertEq( + stakerStrategyListLengthBefore, + stakerStrategyListLengthAfter, + "stakerStrategyListLength shouldn't have changed" + ); } - function test_addSharesRevertsWhenSharesIsZero() external { - // replace dummyStrat with Reenterer contract - reenterer = new Reenterer(); - dummyStrat = StrategyBase(address(reenterer)); - - // whitelist the strategy for deposit - cheats.startPrank(strategyManager.owner()); - IStrategy[] memory _strategy = new IStrategy[](1); - _strategy[0] = dummyStrat; - cheats.expectEmit(true, true, true, true, address(strategyManager)); - emit StrategyAddedToDepositWhitelist(dummyStrat); - strategyManager.addStrategiesToDepositWhitelist(_strategy); - cheats.stopPrank(); - - address staker = address(this); + /** + * @notice testing removeShares() + * deposits 1 strategy and tests it is removed from staker strategy list after removing all shares + */ + function testFuzz_RemovesStakerStrategyListSingleStrat( + address staker, + uint256 sharesAmount + ) external filterFuzzedAddressInputs(staker) { + cheats.assume(staker != address(0)); + cheats.assume(sharesAmount > 0 && sharesAmount < dummyToken.totalSupply()); IStrategy strategy = dummyStrat; - IERC20 token = dummyToken; - uint256 amount = 1e18; - - reenterer.prepareReturnData(abi.encode(uint256(0))); - - cheats.startPrank(staker); - cheats.expectRevert(bytes("StrategyManager._addShares: shares should not be zero!")); - strategyManager.depositIntoStrategy(strategy, token, amount); - cheats.stopPrank(); - } - - function test_addSharesRevertsWhenDepositWouldExeedMaxArrayLength() external { - address staker = address(this); - IERC20 token = dummyToken; - uint256 amount = 1e18; - IStrategy strategy = dummyStrat; - - // uint256 MAX_STAKER_STRATEGY_LIST_LENGTH = strategyManager.MAX_STAKER_STRATEGY_LIST_LENGTH(); - uint256 MAX_STAKER_STRATEGY_LIST_LENGTH = 32; - - // loop that deploys a new strategy and deposits into it - for (uint256 i = 0; i < MAX_STAKER_STRATEGY_LIST_LENGTH; ++i) { - cheats.startPrank(staker); - strategyManager.depositIntoStrategy(strategy, token, amount); - cheats.stopPrank(); - - dummyStrat = deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin); - strategy = dummyStrat; - - // whitelist the strategy for deposit - cheats.startPrank(strategyManager.owner()); - IStrategy[] memory _strategy = new IStrategy[](1); - _strategy[0] = dummyStrat; - cheats.expectEmit(true, true, true, true, address(strategyManager)); - emit StrategyAddedToDepositWhitelist(dummyStrat); - strategyManager.addStrategiesToDepositWhitelist(_strategy); - cheats.stopPrank(); - } - - require( - strategyManager.stakerStrategyListLength(staker) == MAX_STAKER_STRATEGY_LIST_LENGTH, - "strategyManager.stakerStrategyListLength(staker) != MAX_STAKER_STRATEGY_LIST_LENGTH" - ); - - cheats.startPrank(staker); - cheats.expectRevert(bytes("StrategyManager._addShares: deposit would exceed MAX_STAKER_STRATEGY_LIST_LENGTH")); - strategyManager.depositIntoStrategy(strategy, token, amount); - cheats.stopPrank(); - } - - function test_depositIntoStrategyRevertsWhenTokenSafeTransferFromReverts() external { - // replace 'dummyStrat' with one that uses a reverting token - dummyToken = IERC20(address(new Reverter())); - dummyStrat = deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin); - - // whitelist the strategy for deposit - cheats.startPrank(strategyManager.owner()); - IStrategy[] memory _strategy = new IStrategy[](1); - _strategy[0] = dummyStrat; - cheats.expectEmit(true, true, true, true, address(strategyManager)); - emit StrategyAddedToDepositWhitelist(dummyStrat); - strategyManager.addStrategiesToDepositWhitelist(_strategy); - cheats.stopPrank(); - - address staker = address(this); - IERC20 token = dummyToken; - uint256 amount = 1e18; - IStrategy strategy = dummyStrat; - - cheats.startPrank(staker); - cheats.expectRevert(); - strategyManager.depositIntoStrategy(strategy, token, amount); - cheats.stopPrank(); - } - - function test_depositIntoStrategyRevertsWhenTokenDoesNotExist() external { - // replace 'dummyStrat' with one that uses a non-existent token - dummyToken = IERC20(address(5678)); - dummyStrat = deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin); - - // whitelist the strategy for deposit - cheats.startPrank(strategyManager.owner()); - IStrategy[] memory _strategy = new IStrategy[](1); - _strategy[0] = dummyStrat; - cheats.expectEmit(true, true, true, true, address(strategyManager)); - emit StrategyAddedToDepositWhitelist(dummyStrat); - strategyManager.addStrategiesToDepositWhitelist(_strategy); - cheats.stopPrank(); - - address staker = address(this); - IERC20 token = dummyToken; - uint256 amount = 1e18; - IStrategy strategy = dummyStrat; - - cheats.startPrank(staker); - cheats.expectRevert(); - strategyManager.depositIntoStrategy(strategy, token, amount); - cheats.stopPrank(); - } - - function test_depositIntoStrategyRevertsWhenStrategyDepositFunctionReverts() external { - // replace 'dummyStrat' with one that always reverts - dummyStrat = StrategyBase(address(new Reverter())); - - // whitelist the strategy for deposit - cheats.startPrank(strategyManager.owner()); - IStrategy[] memory _strategy = new IStrategy[](1); - _strategy[0] = dummyStrat; - cheats.expectEmit(true, true, true, true, address(strategyManager)); - emit StrategyAddedToDepositWhitelist(dummyStrat); - strategyManager.addStrategiesToDepositWhitelist(_strategy); - cheats.stopPrank(); - - address staker = address(this); - IERC20 token = dummyToken; - uint256 amount = 1e18; - IStrategy strategy = dummyStrat; - - cheats.startPrank(staker); - cheats.expectRevert(); - strategyManager.depositIntoStrategy(strategy, token, amount); - cheats.stopPrank(); - } - - function test_depositIntoStrategyRevertsWhenStrategyDoesNotExist() external { - // replace 'dummyStrat' with one that does not exist - dummyStrat = StrategyBase(address(5678)); - - // whitelist the strategy for deposit - cheats.startPrank(strategyManager.owner()); - IStrategy[] memory _strategy = new IStrategy[](1); - _strategy[0] = dummyStrat; - cheats.expectEmit(true, true, true, true, address(strategyManager)); - emit StrategyAddedToDepositWhitelist(dummyStrat); - strategyManager.addStrategiesToDepositWhitelist(_strategy); - cheats.stopPrank(); - - address staker = address(this); - IERC20 token = dummyToken; - uint256 amount = 1e18; - IStrategy strategy = dummyStrat; - - cheats.startPrank(staker); - cheats.expectRevert(); - strategyManager.depositIntoStrategy(strategy, token, amount); - cheats.stopPrank(); - } - - function test_depositIntoStrategyRevertsWhenStrategyNotWhitelisted() external { - // replace 'dummyStrat' with one that is not whitelisted - dummyStrat = deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin); - - address staker = address(this); - IERC20 token = dummyToken; - uint256 amount = 1e18; - IStrategy strategy = dummyStrat; - - cheats.startPrank(staker); - cheats.expectRevert("StrategyManager.onlyStrategiesWhitelistedForDeposit: strategy not whitelisted"); - strategyManager.depositIntoStrategy(strategy, token, amount); - cheats.stopPrank(); - } - - function testSetStrategyWhitelister(address newWhitelister) external { - address previousStrategyWhitelister = strategyManager.strategyWhitelister(); - cheats.expectEmit(true, true, true, true, address(strategyManager)); - emit StrategyWhitelisterChanged(previousStrategyWhitelister, newWhitelister); - strategyManager.setStrategyWhitelister(newWhitelister); - require( - strategyManager.strategyWhitelister() == newWhitelister, - "strategyManager.strategyWhitelister() != newWhitelister" - ); - } - - function testSetStrategyWhitelisterRevertsWhenCalledByNotOwner( - address notOwner - ) external filterFuzzedAddressInputs(notOwner) { - cheats.assume(notOwner != strategyManager.owner()); - address newWhitelister = address(this); - cheats.startPrank(notOwner); - cheats.expectRevert(bytes("Ownable: caller is not the owner")); - strategyManager.setStrategyWhitelister(newWhitelister); - cheats.stopPrank(); - } - - function testAddStrategiesToDepositWhitelist(uint8 numberOfStrategiesToAdd) public returns (IStrategy[] memory) { - // sanity filtering on fuzzed input - cheats.assume(numberOfStrategiesToAdd <= 16); - - IStrategy[] memory strategyArray = new IStrategy[](numberOfStrategiesToAdd); - // loop that deploys a new strategy and adds it to the array - for (uint256 i = 0; i < numberOfStrategiesToAdd; ++i) { - IStrategy _strategy = deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin); - strategyArray[i] = _strategy; - require(!strategyManager.strategyIsWhitelistedForDeposit(_strategy), "strategy improperly whitelisted?"); - } - - cheats.startPrank(strategyManager.strategyWhitelister()); - for (uint256 i = 0; i < numberOfStrategiesToAdd; ++i) { - cheats.expectEmit(true, true, true, true, address(strategyManager)); - emit StrategyAddedToDepositWhitelist(strategyArray[i]); - } - strategyManager.addStrategiesToDepositWhitelist(strategyArray); - cheats.stopPrank(); - - for (uint256 i = 0; i < numberOfStrategiesToAdd; ++i) { - require( - strategyManager.strategyIsWhitelistedForDeposit(strategyArray[i]), - "strategy not properly whitelisted" - ); - } - - return strategyArray; - } - - function testAddStrategiesToDepositWhitelistRevertsWhenCalledByNotStrategyWhitelister( - address notStrategyWhitelister - ) external filterFuzzedAddressInputs(notStrategyWhitelister) { - cheats.assume(notStrategyWhitelister != strategyManager.strategyWhitelister()); - IStrategy[] memory strategyArray = new IStrategy[](1); - IStrategy _strategy = deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin); - strategyArray[0] = _strategy; - - cheats.startPrank(notStrategyWhitelister); - cheats.expectRevert(bytes("StrategyManager.onlyStrategyWhitelister: not the strategyWhitelister")); - strategyManager.addStrategiesToDepositWhitelist(strategyArray); - cheats.stopPrank(); - } - - function testRemoveStrategiesFromDepositWhitelist( - uint8 numberOfStrategiesToAdd, - uint8 numberOfStrategiesToRemove - ) external { - // sanity filtering on fuzzed input - cheats.assume(numberOfStrategiesToAdd <= 16); - cheats.assume(numberOfStrategiesToRemove <= 16); - cheats.assume(numberOfStrategiesToRemove <= numberOfStrategiesToAdd); - - IStrategy[] memory strategiesAdded = testAddStrategiesToDepositWhitelist(numberOfStrategiesToAdd); - - IStrategy[] memory strategiesToRemove = new IStrategy[](numberOfStrategiesToRemove); - // loop that selectively copies from array to other array - for (uint256 i = 0; i < numberOfStrategiesToRemove; ++i) { - strategiesToRemove[i] = strategiesAdded[i]; - } - - cheats.startPrank(strategyManager.strategyWhitelister()); - for (uint256 i = 0; i < strategiesToRemove.length; ++i) { - cheats.expectEmit(true, true, true, true, address(strategyManager)); - emit StrategyRemovedFromDepositWhitelist(strategiesToRemove[i]); - } - strategyManager.removeStrategiesFromDepositWhitelist(strategiesToRemove); - cheats.stopPrank(); - - for (uint256 i = 0; i < numberOfStrategiesToAdd; ++i) { - if (i < numberOfStrategiesToRemove) { - require( - !strategyManager.strategyIsWhitelistedForDeposit(strategiesToRemove[i]), - "strategy not properly removed from whitelist" - ); - } else { - require( - strategyManager.strategyIsWhitelistedForDeposit(strategiesAdded[i]), - "strategy improperly removed from whitelist?" - ); - } - } - } - - function testRemoveStrategiesFromDepositWhitelistRevertsWhenCalledByNotStrategyWhitelister( - address notStrategyWhitelister - ) external filterFuzzedAddressInputs(notStrategyWhitelister) { - cheats.assume(notStrategyWhitelister != strategyManager.strategyWhitelister()); - IStrategy[] memory strategyArray = testAddStrategiesToDepositWhitelist(1); - - cheats.startPrank(notStrategyWhitelister); - cheats.expectRevert(bytes("StrategyManager.onlyStrategyWhitelister: not the strategyWhitelister")); - strategyManager.removeStrategiesFromDepositWhitelist(strategyArray); - cheats.stopPrank(); - } - - function testAddSharesRevertsDelegationManagerModifier() external { - DelegationManagerMock invalidDelegationManager = new DelegationManagerMock(); - cheats.expectRevert(bytes("StrategyManager.onlyDelegationManager: not the DelegationManager")); - invalidDelegationManager.addShares(strategyManager, address(this), dummyStrat, 1); - } - - function testAddSharesRevertsStakerZeroAddress(uint256 amount) external { - cheats.expectRevert(bytes("StrategyManager._addShares: staker cannot be zero address")); - delegationManagerMock.addShares(strategyManager, address(0), dummyStrat, amount); - } - - function testAddSharesRevertsZeroShares(address staker) external { - cheats.assume(staker != address(0)); - cheats.expectRevert(bytes("StrategyManager._addShares: shares should not be zero!")); - delegationManagerMock.addShares(strategyManager, staker, dummyStrat, 0); - } - - function testAddSharesAppendsStakerStrategyList(address staker, uint256 amount) external { - cheats.assume(staker != address(0) && amount != 0); - uint256 stakerStrategyListLengthBefore = strategyManager.stakerStrategyListLength(staker); - uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, dummyStrat); - require(sharesBefore == 0, "Staker has already deposited into this strategy"); - require(!_isDepositedStrategy(staker, dummyStrat), "strategy shouldn't be deposited"); - - delegationManagerMock.addShares(strategyManager, staker, dummyStrat, amount); - uint256 stakerStrategyListLengthAfter = strategyManager.stakerStrategyListLength(staker); - uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, dummyStrat); - require( - stakerStrategyListLengthAfter == stakerStrategyListLengthBefore + 1, - "stakerStrategyListLengthAfter != stakerStrategyListLengthBefore + 1" - ); - require(sharesAfter == amount, "sharesAfter != amount"); - require(_isDepositedStrategy(staker, dummyStrat), "strategy should be deposited"); - } - - function testAddSharesExistingShares(address staker, uint256 sharesAmount) external { - cheats.assume(staker != address(0) && 0 < sharesAmount && sharesAmount <= dummyToken.totalSupply()); - uint256 initialAmount = 1e18; - IStrategy strategy = dummyStrat; - _depositIntoStrategySuccessfully(strategy, staker, initialAmount); - uint256 stakerStrategyListLengthBefore = strategyManager.stakerStrategyListLength(staker); - uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, dummyStrat); - require(sharesBefore == initialAmount, "Staker has not deposited into strategy"); - require(_isDepositedStrategy(staker, strategy), "strategy should be deposited"); - - delegationManagerMock.addShares(strategyManager, staker, dummyStrat, sharesAmount); - uint256 stakerStrategyListLengthAfter = strategyManager.stakerStrategyListLength(staker); - uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, dummyStrat); - require( - stakerStrategyListLengthAfter == stakerStrategyListLengthBefore, - "stakerStrategyListLengthAfter != stakerStrategyListLengthBefore" - ); - require(sharesAfter == sharesBefore + sharesAmount, "sharesAfter != sharesBefore + amount"); - require(_isDepositedStrategy(staker, strategy), "strategy should be deposited"); - } - - function testRemoveSharesRevertsDelegationManagerModifier() external { - DelegationManagerMock invalidDelegationManager = new DelegationManagerMock(); - cheats.expectRevert(bytes("StrategyManager.onlyDelegationManager: not the DelegationManager")); - invalidDelegationManager.removeShares(strategyManager, address(this), dummyStrat, 1); - } - - function testRemoveSharesRevertsShareAmountTooHigh( - address staker, - uint256 depositAmount, - uint256 removeSharesAmount - ) external { - cheats.assume(staker != address(0)); - cheats.assume(depositAmount > 0 && depositAmount < dummyToken.totalSupply()); - cheats.assume(removeSharesAmount > depositAmount); - IStrategy strategy = dummyStrat; - _depositIntoStrategySuccessfully(strategy, staker, depositAmount); - cheats.expectRevert(bytes("StrategyManager._removeShares: shareAmount too high")); - delegationManagerMock.removeShares(strategyManager, staker, strategy, removeSharesAmount); - } - - function testRemoveSharesRemovesStakerStrategyListSingleStrat(address staker, uint256 sharesAmount) external { - cheats.assume(staker != address(0)); - cheats.assume(sharesAmount > 0 && sharesAmount < dummyToken.totalSupply()); - IStrategy strategy = dummyStrat; - _depositIntoStrategySuccessfully(strategy, staker, sharesAmount); + _depositIntoStrategySuccessfully(strategy, staker, sharesAmount); uint256 stakerStrategyListLengthBefore = strategyManager.stakerStrategyListLength(staker); uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategy); - require(sharesBefore == sharesAmount, "Staker has not deposited amount into strategy"); + assertEq(sharesBefore, sharesAmount, "Staker has not deposited amount into strategy"); delegationManagerMock.removeShares(strategyManager, staker, strategy, sharesAmount); uint256 stakerStrategyListLengthAfter = strategyManager.stakerStrategyListLength(staker); uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategy); - require( - stakerStrategyListLengthAfter == stakerStrategyListLengthBefore - 1, + assertEq( + stakerStrategyListLengthAfter, + stakerStrategyListLengthBefore - 1, "stakerStrategyListLengthAfter != stakerStrategyListLengthBefore - 1" ); - require(sharesAfter == 0, "sharesAfter != 0"); - require(!_isDepositedStrategy(staker, strategy), "strategy should not be part of staker strategy list"); + assertEq(sharesAfter, 0, "sharesAfter != 0"); + assertFalse(_isDepositedStrategy(staker, strategy), "strategy should not be part of staker strategy list"); } - // Remove Strategy from staker strategy list with multiple strategies deposited - function testRemoveSharesRemovesStakerStrategyListMultipleStrat( + /** + * @notice testing removeShares() function with 3 strategies deposited. + * Randomly selects one of the 3 strategies to be fully removed from staker strategy list. + * Only callable by DelegationManager + */ + function testFuzz_RemovesStakerStrategyListMultipleStrat( address staker, uint256[3] memory amounts, uint8 randStrategy - ) external { + ) external filterFuzzedAddressInputs(staker) { cheats.assume(staker != address(0)); IStrategy[] memory strategies = new IStrategy[](3); strategies[0] = dummyStrat; @@ -949,23 +946,31 @@ contract StrategyManagerUnitTests is Test, Utils { uint256[] memory sharesBefore = new uint256[](3); for (uint256 i = 0; i < 3; ++i) { sharesBefore[i] = strategyManager.stakerStrategyShares(staker, strategies[i]); - require(sharesBefore[i] == amounts[i], "Staker has not deposited amount into strategy"); - require(_isDepositedStrategy(staker, strategies[i]), "strategy should be deposited"); + assertEq(sharesBefore[i], amounts[i], "Staker has not deposited amount into strategy"); + assertTrue(_isDepositedStrategy(staker, strategies[i]), "strategy should be deposited"); } delegationManagerMock.removeShares(strategyManager, staker, removeStrategy, removeAmount); uint256 stakerStrategyListLengthAfter = strategyManager.stakerStrategyListLength(staker); uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, removeStrategy); - require( - stakerStrategyListLengthAfter == stakerStrategyListLengthBefore - 1, + assertEq( + stakerStrategyListLengthAfter, + stakerStrategyListLengthBefore - 1, "stakerStrategyListLengthAfter != stakerStrategyListLengthBefore - 1" ); - require(sharesAfter == 0, "sharesAfter != 0"); - require(!_isDepositedStrategy(staker, removeStrategy), "strategy should not be part of staker strategy list"); + assertEq(sharesAfter, 0, "sharesAfter != 0"); + assertFalse( + _isDepositedStrategy(staker, removeStrategy), + "strategy should not be part of staker strategy list" + ); } - // removeShares() from staker strategy list with multiple strategies deposited. Only callable by DelegationManager - function testRemoveShares(uint256[3] memory depositAmounts, uint256[3] memory sharesAmounts) external { + /** + * @notice testing removeShares() function with 3 strategies deposited. + * Removing Shares could result in removing from staker strategy list if depositAmounts[i] == sharesAmounts[i]. + * Only callable by DelegationManager + */ + function testFuzz_RemoveShares(uint256[3] memory depositAmounts, uint256[3] memory sharesAmounts) external { address staker = address(this); IStrategy[] memory strategies = new IStrategy[](3); strategies[0] = dummyStrat; @@ -976,8 +981,8 @@ contract StrategyManagerUnitTests is Test, Utils { cheats.assume(sharesAmounts[i] > 0 && sharesAmounts[i] <= depositAmounts[i]); _depositIntoStrategySuccessfully(strategies[i], staker, depositAmounts[i]); sharesBefore[i] = strategyManager.stakerStrategyShares(staker, strategies[i]); - require(sharesBefore[i] == depositAmounts[i], "Staker has not deposited amount into strategy"); - require(_isDepositedStrategy(staker, strategies[i]), "strategy should be deposited"); + assertEq(sharesBefore[i], depositAmounts[i], "Staker has not deposited amount into strategy"); + assertTrue(_isDepositedStrategy(staker, strategies[i]), "strategy should be deposited"); } uint256 stakerStrategyListLengthBefore = strategyManager.stakerStrategyListLength(staker); @@ -985,53 +990,175 @@ contract StrategyManagerUnitTests is Test, Utils { uint256[] memory sharesAfter = new uint256[](3); for (uint256 i = 0; i < 3; ++i) { delegationManagerMock.removeShares(strategyManager, staker, strategies[i], sharesAmounts[i]); + } + + for (uint256 i = 0; i < 3; ++i) { sharesAfter[i] = strategyManager.stakerStrategyShares(staker, strategies[i]); if (sharesAmounts[i] == depositAmounts[i]) { ++numPoppedStrategies; - require( - !_isDepositedStrategy(staker, strategies[i]), + assertFalse( + _isDepositedStrategy(staker, strategies[i]), "strategy should not be part of staker strategy list" ); - require(sharesAfter[i] == 0, "sharesAfter != 0"); + assertEq(sharesAfter[i], 0, "sharesAfter != 0"); } else { - require(_isDepositedStrategy(staker, strategies[i]), "strategy should be part of staker strategy list"); - require( - sharesAfter[i] == sharesBefore[i] - sharesAmounts[i], + assertTrue( + _isDepositedStrategy(staker, strategies[i]), + "strategy should be part of staker strategy list" + ); + assertEq( + sharesAfter[i], + sharesBefore[i] - sharesAmounts[i], "sharesAfter != sharesBefore - sharesAmounts" ); } } - require( - stakerStrategyListLengthBefore - numPoppedStrategies == strategyManager.stakerStrategyListLength(staker), + assertEq( + stakerStrategyListLengthBefore - numPoppedStrategies, + strategyManager.stakerStrategyListLength(staker), "stakerStrategyListLengthBefore - numPoppedStrategies != strategyManager.stakerStrategyListLength(staker)" ); } +} + +contract StrategyManagerUnitTests_addShares is StrategyManagerUnitTests { + function test_Revert_DelegationManagerModifier() external { + DelegationManagerMock invalidDelegationManager = new DelegationManagerMock(); + cheats.expectRevert("StrategyManager.onlyDelegationManager: not the DelegationManager"); + invalidDelegationManager.addShares(strategyManager, address(this), dummyStrat, 1); + } + + function testFuzz_Revert_StakerZeroAddress(uint256 amount) external { + cheats.expectRevert("StrategyManager._addShares: staker cannot be zero address"); + delegationManagerMock.addShares(strategyManager, address(0), dummyStrat, amount); + } + + function testFuzz_Revert_ZeroShares(address staker) external filterFuzzedAddressInputs(staker) { + cheats.assume(staker != address(0)); + cheats.expectRevert("StrategyManager._addShares: shares should not be zero!"); + delegationManagerMock.addShares(strategyManager, staker, dummyStrat, 0); + } + + function testFuzz_AppendsStakerStrategyList( + address staker, + uint256 amount + ) external filterFuzzedAddressInputs(staker) { + cheats.assume(staker != address(0) && amount != 0); + uint256 stakerStrategyListLengthBefore = strategyManager.stakerStrategyListLength(staker); + uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, dummyStrat); + assertEq(sharesBefore, 0, "Staker has already deposited into this strategy"); + assertFalse(_isDepositedStrategy(staker, dummyStrat), "strategy should not be deposited"); + + delegationManagerMock.addShares(strategyManager, staker, dummyStrat, amount); + uint256 stakerStrategyListLengthAfter = strategyManager.stakerStrategyListLength(staker); + uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, dummyStrat); + assertEq( + stakerStrategyListLengthAfter, + stakerStrategyListLengthBefore + 1, + "stakerStrategyListLengthAfter != stakerStrategyListLengthBefore + 1" + ); + assertEq(sharesAfter, amount, "sharesAfter != amount"); + assertTrue(_isDepositedStrategy(staker, dummyStrat), "strategy should be deposited"); + } + + function testFuzz_AddSharesToExistingShares( + address staker, + uint256 sharesAmount + ) external filterFuzzedAddressInputs(staker) { + cheats.assume(staker != address(0) && 0 < sharesAmount && sharesAmount <= dummyToken.totalSupply()); + uint256 initialAmount = 1e18; + IStrategy strategy = dummyStrat; + _depositIntoStrategySuccessfully(strategy, staker, initialAmount); + uint256 stakerStrategyListLengthBefore = strategyManager.stakerStrategyListLength(staker); + uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, dummyStrat); + assertEq(sharesBefore, initialAmount, "Staker has not deposited amount into strategy"); + assertTrue(_isDepositedStrategy(staker, strategy), "strategy should be deposited"); + + delegationManagerMock.addShares(strategyManager, staker, dummyStrat, sharesAmount); + uint256 stakerStrategyListLengthAfter = strategyManager.stakerStrategyListLength(staker); + uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, dummyStrat); + assertEq( + stakerStrategyListLengthAfter, + stakerStrategyListLengthBefore, + "stakerStrategyListLengthAfter != stakerStrategyListLengthBefore" + ); + assertEq(sharesAfter, sharesBefore + sharesAmount, "sharesAfter != sharesBefore + sharesAmount"); + assertTrue(_isDepositedStrategy(staker, strategy), "strategy should be deposited"); + } + + /** + * @notice When _addShares() called either by depositIntoStrategy or addShares() results in appending to + * stakerStrategyListLength when the staker has MAX_STAKER_STRATEGY_LIST_LENGTH strategies, it should revert + */ + function test_Revert_WhenMaxStrategyListLength() external { + address staker = address(this); + IERC20 token = dummyToken; + uint256 amount = 1e18; + IStrategy strategy = dummyStrat; + uint256 MAX_STAKER_STRATEGY_LIST_LENGTH = 32; + + // loop that deploys a new strategy and deposits into it + for (uint256 i = 0; i < MAX_STAKER_STRATEGY_LIST_LENGTH; ++i) { + cheats.startPrank(staker); + strategyManager.depositIntoStrategy(strategy, token, amount); + cheats.stopPrank(); + + dummyStrat = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin); + strategy = dummyStrat; + + // whitelist the strategy for deposit + cheats.startPrank(strategyManager.owner()); + IStrategy[] memory _strategy = new IStrategy[](1); + _strategy[0] = dummyStrat; + strategyManager.addStrategiesToDepositWhitelist(_strategy); + cheats.stopPrank(); + } + + assertEq( + strategyManager.stakerStrategyListLength(staker), + MAX_STAKER_STRATEGY_LIST_LENGTH, + "strategyMananger.stakerStrategyListLength(staker) != MAX_STAKER_STRATEGY_LIST_LENGTH" + ); + + cheats.prank(staker); + cheats.expectRevert("StrategyManager._addShares: deposit would exceed MAX_STAKER_STRATEGY_LIST_LENGTH"); + delegationManagerMock.addShares(strategyManager, staker, strategy, amount); + + cheats.expectRevert("StrategyManager._addShares: deposit would exceed MAX_STAKER_STRATEGY_LIST_LENGTH"); + strategyManager.depositIntoStrategy(strategy, token, amount); + } +} - function testWithdrawSharesAsTokensRevertsDelegationManagerModifier() external { +contract StrategyManagerUnitTests_withdrawSharesAsTokens is StrategyManagerUnitTests { + function test_Revert_DelegationManagerModifier() external { DelegationManagerMock invalidDelegationManager = new DelegationManagerMock(); - cheats.expectRevert(bytes("StrategyManager.onlyDelegationManager: not the DelegationManager")); + cheats.expectRevert("StrategyManager.onlyDelegationManager: not the DelegationManager"); invalidDelegationManager.removeShares(strategyManager, address(this), dummyStrat, 1); } - function testWithdrawSharesAsTokensRevertsShareAmountTooHigh( + /** + * @notice deposits a single strategy and withdrawSharesAsTokens() function reverts when sharesAmount is + * higher than depositAmount + */ + function testFuzz_Revert_ShareAmountTooHigh( address staker, uint256 depositAmount, uint256 sharesAmount - ) external { + ) external filterFuzzedAddressInputs(staker) { cheats.assume(staker != address(0)); cheats.assume(depositAmount > 0 && depositAmount < dummyToken.totalSupply() && depositAmount < sharesAmount); IStrategy strategy = dummyStrat; IERC20 token = dummyToken; _depositIntoStrategySuccessfully(strategy, staker, depositAmount); - cheats.expectRevert(bytes("StrategyBase.withdraw: amountShares must be less than or equal to totalShares")); + cheats.expectRevert("StrategyBase.withdraw: amountShares must be less than or equal to totalShares"); delegationManagerMock.withdrawSharesAsTokens(strategyManager, staker, strategy, sharesAmount, token); } - function testWithdrawSharesAsTokensSingleStrategyDeposited( + function testFuzz_SingleStrategyDeposited( address staker, uint256 depositAmount, uint256 sharesAmount - ) external { + ) external filterFuzzedAddressInputs(staker) { cheats.assume(staker != address(0)); cheats.assume(sharesAmount > 0 && sharesAmount < dummyToken.totalSupply() && depositAmount >= sharesAmount); IStrategy strategy = dummyStrat; @@ -1040,194 +1167,176 @@ contract StrategyManagerUnitTests is Test, Utils { uint256 balanceBefore = token.balanceOf(staker); delegationManagerMock.withdrawSharesAsTokens(strategyManager, staker, strategy, sharesAmount, token); uint256 balanceAfter = token.balanceOf(staker); - require(balanceAfter == balanceBefore + sharesAmount, "balanceAfter != balanceBefore + sharesAmount"); + assertEq(balanceAfter, balanceBefore + sharesAmount, "balanceAfter != balanceBefore + sharesAmount"); } +} - // INTERNAL / HELPER FUNCTIONS - function _setUpQueuedWithdrawalStructSingleStrat( - address staker, - address withdrawer, - IERC20 token, - IStrategy strategy, - uint256 shareAmount - ) - internal - view - returns ( - IDelegationManager.Withdrawal memory queuedWithdrawal, - IERC20[] memory tokensArray, - bytes32 withdrawalRoot - ) - { - IStrategy[] memory strategyArray = new IStrategy[](1); - tokensArray = new IERC20[](1); - uint256[] memory shareAmounts = new uint256[](1); - strategyArray[0] = strategy; - tokensArray[0] = token; - shareAmounts[0] = shareAmount; - queuedWithdrawal = IDelegationManager.Withdrawal({ - strategies: strategyArray, - shares: shareAmounts, - staker: staker, - withdrawer: withdrawer, - nonce: delegationManagerMock.cumulativeWithdrawalsQueued(staker), - startBlock: uint32(block.number), - delegatedTo: strategyManager.delegation().delegatedTo(staker) - }); - // calculate the withdrawal root - withdrawalRoot = delegationManagerMock.calculateWithdrawalRoot(queuedWithdrawal); - return (queuedWithdrawal, tokensArray, withdrawalRoot); +contract StrategyManagerUnitTests_setStrategyWhitelister is StrategyManagerUnitTests { + function testFuzz_SetStrategyWhitelister( + address newWhitelister + ) external filterFuzzedAddressInputs(newWhitelister) { + address previousStrategyWhitelister = strategyManager.strategyWhitelister(); + cheats.expectEmit(true, true, true, true, address(strategyManager)); + emit StrategyWhitelisterChanged(previousStrategyWhitelister, newWhitelister); + strategyManager.setStrategyWhitelister(newWhitelister); + assertEq( + strategyManager.strategyWhitelister(), + newWhitelister, + "strategyManager.strategyWhitelister() != newWhitelister" + ); } - function _depositIntoStrategySuccessfully( - IStrategy strategy, - address staker, - uint256 amount - ) internal filterFuzzedAddressInputs(staker) { - IERC20 token = dummyToken; - - // filter out zero case since it will revert with "StrategyManager._addShares: shares should not be zero!" - cheats.assume(amount != 0); - // filter out zero address because the mock ERC20 we are using will revert on using it - cheats.assume(staker != address(0)); - // sanity check / filter - cheats.assume(amount <= token.balanceOf(address(this))); + function testFuzz_Revert_WhenCalledByNotOwner(address notOwner) external filterFuzzedAddressInputs(notOwner) { + cheats.assume(notOwner != strategyManager.owner()); + address newWhitelister = address(this); + cheats.prank(notOwner); + cheats.expectRevert("Ownable: caller is not the owner"); + strategyManager.setStrategyWhitelister(newWhitelister); + } +} - uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, strategy); - uint256 stakerStrategyListLengthBefore = strategyManager.stakerStrategyListLength(staker); +contract StrategyManagerUnitTests_addStrategiesToDepositWhitelist is StrategyManagerUnitTests { + function testFuzz_Revert_WhenCalledByNotStrategyWhitelister( + address notStrategyWhitelister + ) external filterFuzzedAddressInputs(notStrategyWhitelister) { + cheats.assume(notStrategyWhitelister != strategyManager.strategyWhitelister()); + IStrategy[] memory strategyArray = new IStrategy[](1); + IStrategy _strategy = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin); + strategyArray[0] = _strategy; - // needed for expecting an event with the right parameters - uint256 expectedShares = amount; + cheats.prank(notStrategyWhitelister); + cheats.expectRevert("StrategyManager.onlyStrategyWhitelister: not the strategyWhitelister"); + strategyManager.addStrategiesToDepositWhitelist(strategyArray); + } - cheats.startPrank(staker); + function test_AddSingleStrategyToWhitelist() external { + IStrategy[] memory strategyArray = new IStrategy[](1); + IStrategy strategy = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin); + strategyArray[0] = strategy; + assertFalse(strategyManager.strategyIsWhitelistedForDeposit(strategy), "strategy should not be whitelisted"); cheats.expectEmit(true, true, true, true, address(strategyManager)); - emit Deposit(staker, token, strategy, expectedShares); - uint256 shares = strategyManager.depositIntoStrategy(strategy, token, amount); - cheats.stopPrank(); - - uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, strategy); - uint256 stakerStrategyListLengthAfter = strategyManager.stakerStrategyListLength(staker); + emit StrategyAddedToDepositWhitelist(strategy); + strategyManager.addStrategiesToDepositWhitelist(strategyArray); + assertTrue(strategyManager.strategyIsWhitelistedForDeposit(strategy), "strategy should be whitelisted"); + } - require(sharesAfter == sharesBefore + shares, "sharesAfter != sharesBefore + shares"); - if (sharesBefore == 0) { - require( - stakerStrategyListLengthAfter == stakerStrategyListLengthBefore + 1, - "stakerStrategyListLengthAfter != stakerStrategyListLengthBefore + 1" - ); - require( - strategyManager.stakerStrategyList(staker, stakerStrategyListLengthAfter - 1) == strategy, - "strategyManager.stakerStrategyList(staker, stakerStrategyListLengthAfter - 1) != strategy" - ); - } + function test_AddAlreadyWhitelistedStrategy() external { + IStrategy[] memory strategyArray = new IStrategy[](1); + IStrategy strategy = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin); + strategyArray[0] = strategy; + assertFalse(strategyManager.strategyIsWhitelistedForDeposit(strategy), "strategy should not be whitelisted"); + cheats.expectEmit(true, true, true, true, address(strategyManager)); + emit StrategyAddedToDepositWhitelist(strategy); + strategyManager.addStrategiesToDepositWhitelist(strategyArray); + assertTrue(strategyManager.strategyIsWhitelistedForDeposit(strategy), "strategy should be whitelisted"); + // Make sure event not emitted by checking logs length + cheats.recordLogs(); + uint256 numLogsBefore = cheats.getRecordedLogs().length; + strategyManager.addStrategiesToDepositWhitelist(strategyArray); + uint256 numLogsAfter = cheats.getRecordedLogs().length; + assertEq(numLogsBefore, numLogsAfter, "event emitted when strategy already whitelisted"); + assertTrue(strategyManager.strategyIsWhitelistedForDeposit(strategy), "strategy should still be whitelisted"); } - function _setUpQueuedWithdrawalStructSingleStrat_MultipleStrategies( - address staker, - address withdrawer, - IStrategy[] memory strategyArray, - uint256[] memory shareAmounts - ) internal view returns (IDelegationManager.Withdrawal memory queuedWithdrawal, bytes32 withdrawalRoot) { - queuedWithdrawal = IDelegationManager.Withdrawal({ - strategies: strategyArray, - shares: shareAmounts, - staker: staker, - withdrawer: withdrawer, - nonce: delegationManagerMock.cumulativeWithdrawalsQueued(staker), - startBlock: uint32(block.number), - delegatedTo: strategyManager.delegation().delegatedTo(staker) - }); - // calculate the withdrawal root - withdrawalRoot = delegationManagerMock.calculateWithdrawalRoot(queuedWithdrawal); - return (queuedWithdrawal, withdrawalRoot); + function testFuzz_AddStrategiesToDepositWhitelist(uint8 numberOfStrategiesToAdd) external { + // sanity filtering on fuzzed input + cheats.assume(numberOfStrategiesToAdd <= 16); + _addStrategiesToWhitelist(numberOfStrategiesToAdd); } +} + +contract StrategyManagerUnitTests_removeStrategiesFromDepositWhitelist is StrategyManagerUnitTests { + function testFuzz_Revert_WhenCalledByNotStrategyWhitelister( + address notStrategyWhitelister + ) external filterFuzzedAddressInputs(notStrategyWhitelister) { + cheats.assume(notStrategyWhitelister != strategyManager.strategyWhitelister()); + IStrategy[] memory strategyArray = _addStrategiesToWhitelist(1); - function _arrayWithJustDummyToken() internal view returns (IERC20[] memory) { - IERC20[] memory array = new IERC20[](1); - array[0] = dummyToken; - return array; + cheats.prank(notStrategyWhitelister); + cheats.expectRevert("StrategyManager.onlyStrategyWhitelister: not the strategyWhitelister"); + strategyManager.removeStrategiesFromDepositWhitelist(strategyArray); } - function _arrayWithJustTwoDummyTokens() internal view returns (IERC20[] memory) { - IERC20[] memory array = new IERC20[](2); - array[0] = dummyToken; - array[1] = dummyToken; - return array; + /** + * @notice testing that mapping is still false and no event emitted + */ + function test_RemoveNonWhitelistedStrategy() external { + IStrategy[] memory strategyArray = new IStrategy[](1); + IStrategy strategy = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin); + strategyArray[0] = strategy; + assertFalse(strategyManager.strategyIsWhitelistedForDeposit(strategy), "strategy should not be whitelisted"); + // Make sure event not emitted by checking logs length + cheats.recordLogs(); + uint256 numLogsBefore = cheats.getRecordedLogs().length; + strategyManager.removeStrategiesFromDepositWhitelist(strategyArray); + uint256 numLogsAfter = cheats.getRecordedLogs().length; + assertEq(numLogsBefore, numLogsAfter, "event emitted when strategy already not whitelisted"); + assertFalse( + strategyManager.strategyIsWhitelistedForDeposit(strategy), + "strategy still should not be whitelisted" + ); } - // internal function for de-duping code. expects success if `expectedRevertMessage` is empty and expiry is valid. - function _depositIntoStrategyWithSignature( - address staker, - uint256 amount, - uint256 expiry, - string memory expectedRevertMessage - ) internal returns (bytes memory) { - // filter out zero case since it will revert with "StrategyManager._addShares: shares should not be zero!" - cheats.assume(amount != 0); - // sanity check / filter - cheats.assume(amount <= dummyToken.balanceOf(address(this))); + /** + * @notice testing that strategy is removed from whitelist and event is emitted + */ + function test_RemoveWhitelistedStrategy() external { + IStrategy[] memory strategyArray = new IStrategy[](1); + IStrategy strategy = _deployNewStrategy(dummyToken, strategyManager, pauserRegistry, dummyAdmin); + strategyArray[0] = strategy; + assertFalse(strategyManager.strategyIsWhitelistedForDeposit(strategy), "strategy should not be whitelisted"); + // Add strategy to whitelist first + cheats.expectEmit(true, true, true, true, address(strategyManager)); + emit StrategyAddedToDepositWhitelist(strategy); + strategyManager.addStrategiesToDepositWhitelist(strategyArray); + assertTrue(strategyManager.strategyIsWhitelistedForDeposit(strategy), "strategy should be whitelisted"); - uint256 nonceBefore = strategyManager.nonces(staker); - bytes memory signature; + // Now remove strategy from whitelist + cheats.expectEmit(true, true, true, true, address(strategyManager)); + emit StrategyRemovedFromDepositWhitelist(strategy); + strategyManager.removeStrategiesFromDepositWhitelist(strategyArray); + assertFalse( + strategyManager.strategyIsWhitelistedForDeposit(strategy), + "strategy should no longer be whitelisted" + ); + } - { - bytes32 structHash = keccak256( - abi.encode(strategyManager.DEPOSIT_TYPEHASH(), dummyStrat, dummyToken, amount, nonceBefore, expiry) - ); - bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", strategyManager.domainSeparator(), structHash)); + function testFuzz_RemoveStrategiesFromDepositWhitelist( + uint8 numberOfStrategiesToAdd, + uint8 numberOfStrategiesToRemove + ) external { + // sanity filtering on fuzzed input + cheats.assume(numberOfStrategiesToAdd <= 16); + cheats.assume(numberOfStrategiesToRemove <= 16); + cheats.assume(numberOfStrategiesToRemove <= numberOfStrategiesToAdd); - (uint8 v, bytes32 r, bytes32 s) = cheats.sign(privateKey, digestHash); + IStrategy[] memory strategiesAdded = _addStrategiesToWhitelist(numberOfStrategiesToAdd); - signature = abi.encodePacked(r, s, v); + IStrategy[] memory strategiesToRemove = new IStrategy[](numberOfStrategiesToRemove); + // loop that selectively copies from array to other array + for (uint256 i = 0; i < numberOfStrategiesToRemove; ++i) { + strategiesToRemove[i] = strategiesAdded[i]; } - uint256 sharesBefore = strategyManager.stakerStrategyShares(staker, dummyStrat); - - bool expectedRevertMessageIsempty; - { - string memory emptyString; - expectedRevertMessageIsempty = - keccak256(abi.encodePacked(expectedRevertMessage)) == keccak256(abi.encodePacked(emptyString)); - } - if (!expectedRevertMessageIsempty) { - cheats.expectRevert(bytes(expectedRevertMessage)); - } else if (expiry < block.timestamp) { - cheats.expectRevert("StrategyManager.depositIntoStrategyWithSignature: signature expired"); - } else { - // needed for expecting an event with the right parameters - uint256 expectedShares = amount; + cheats.prank(strategyManager.strategyWhitelister()); + for (uint256 i = 0; i < strategiesToRemove.length; ++i) { cheats.expectEmit(true, true, true, true, address(strategyManager)); - emit Deposit(staker, dummyToken, dummyStrat, expectedShares); - } - uint256 shares = strategyManager.depositIntoStrategyWithSignature( - dummyStrat, - dummyToken, - amount, - staker, - expiry, - signature - ); - - uint256 sharesAfter = strategyManager.stakerStrategyShares(staker, dummyStrat); - uint256 nonceAfter = strategyManager.nonces(staker); - - if (expiry >= block.timestamp && expectedRevertMessageIsempty) { - require(sharesAfter == sharesBefore + shares, "sharesAfter != sharesBefore + shares"); - require(nonceAfter == nonceBefore + 1, "nonceAfter != nonceBefore + 1"); + emit StrategyRemovedFromDepositWhitelist(strategiesToRemove[i]); } - return signature; - } + strategyManager.removeStrategiesFromDepositWhitelist(strategiesToRemove); - /** - * @notice internal function to help check if a strategy is part of list of deposited strategies for a staker - * Used to check if removed correctly after withdrawing all shares for a given strategy - */ - function _isDepositedStrategy(address staker, IStrategy strategy) internal view returns (bool) { - uint256 stakerStrategyListLength = strategyManager.stakerStrategyListLength(staker); - for (uint256 i = 0; i < stakerStrategyListLength; ++i) { - if (strategyManager.stakerStrategyList(staker, i) == strategy) { - return true; + for (uint256 i = 0; i < numberOfStrategiesToAdd; ++i) { + if (i < numberOfStrategiesToRemove) { + assertFalse( + strategyManager.strategyIsWhitelistedForDeposit(strategiesToRemove[i]), + "strategy not properly removed from whitelist" + ); + } else { + assertTrue( + strategyManager.strategyIsWhitelistedForDeposit(strategiesAdded[i]), + "strategy improperly removed from whitelist?" + ); } } - return false; } } diff --git a/src/test/utils/EigenLayerUnitTestBase.sol b/src/test/utils/EigenLayerUnitTestBase.sol new file mode 100644 index 000000000..d0b533398 --- /dev/null +++ b/src/test/utils/EigenLayerUnitTestBase.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity =0.8.12; + +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "src/contracts/permissions/PauserRegistry.sol"; +import "forge-std/Test.sol"; + +abstract contract EigenLayerUnitTestBase is Test { + Vm cheats = Vm(HEVM_ADDRESS); + + PauserRegistry public pauserRegistry; + ProxyAdmin public eigenLayerProxyAdmin; + + mapping(address => bool) public addressIsExcludedFromFuzzedInputs; + + address public constant pauser = address(555); + address public constant unpauser = address(556); + + // Helper Functions/Modifiers + modifier filterFuzzedAddressInputs(address fuzzedAddress) { + cheats.assume(!addressIsExcludedFromFuzzedInputs[fuzzedAddress]); + _; + } + + function setUp() public virtual { + address[] memory pausers = new address[](1); + pausers[0] = pauser; + pauserRegistry = new PauserRegistry(pausers, unpauser); + eigenLayerProxyAdmin = new ProxyAdmin(); + + addressIsExcludedFromFuzzedInputs[address(pauserRegistry)] = true; + addressIsExcludedFromFuzzedInputs[address(eigenLayerProxyAdmin)] = true; + } +} diff --git a/src/test/utils/EigenLayerUnitTestSetup.sol b/src/test/utils/EigenLayerUnitTestSetup.sol index 16f0ec921..a6d5bd0cf 100644 --- a/src/test/utils/EigenLayerUnitTestSetup.sol +++ b/src/test/utils/EigenLayerUnitTestSetup.sol @@ -1,22 +1,30 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity =0.8.12; -import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "src/test/mocks/StrategyManagerMock.sol"; +import "src/test/mocks/DelegationManagerMock.sol"; +import "src/test/mocks/SlasherMock.sol"; +import "src/test/mocks/EigenPodManagerMock.sol"; +import "src/test/utils/EigenLayerUnitTestBase.sol"; -import "forge-std/Test.sol"; +abstract contract EigenLayerUnitTestSetup is EigenLayerUnitTestBase { + // Declare Mocks + StrategyManagerMock strategyManagerMock; + DelegationManagerMock public delegationManagerMock; + SlasherMock public slasherMock; + EigenPodManagerMock public eigenPodManagerMock; -abstract contract EigenLayerUnitTestSetup is Test { - Vm cheats = Vm(HEVM_ADDRESS); + function setUp() public virtual override { + EigenLayerUnitTestBase.setUp(); + strategyManagerMock = new StrategyManagerMock(); + delegationManagerMock = new DelegationManagerMock(); + slasherMock = new SlasherMock(); + eigenPodManagerMock = new EigenPodManagerMock(); - mapping(address => bool) public addressIsExcludedFromFuzzedInputs; - - address public constant pauser = address(555); - address public constant unpauser = address(556); - - // Helper Functions/Modifiers - modifier filterFuzzedAddressInputs(address fuzzedAddress) { - cheats.assume(!addressIsExcludedFromFuzzedInputs[fuzzedAddress]); - _; + addressIsExcludedFromFuzzedInputs[address(0)] = true; + addressIsExcludedFromFuzzedInputs[address(strategyManagerMock)] = true; + addressIsExcludedFromFuzzedInputs[address(delegationManagerMock)] = true; + addressIsExcludedFromFuzzedInputs[address(slasherMock)] = true; + addressIsExcludedFromFuzzedInputs[address(eigenPodManagerMock)] = true; } -} \ No newline at end of file +} diff --git a/src/test/unit/Utils.sol b/src/test/utils/Utils.sol similarity index 93% rename from src/test/unit/Utils.sol rename to src/test/utils/Utils.sol index 8958cdb85..acae8d956 100644 --- a/src/test/unit/Utils.sol +++ b/src/test/utils/Utils.sol @@ -1,7 +1,7 @@ pragma solidity =0.8.12; import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import "../../contracts/strategies/StrategyBase.sol"; +import "src/contracts/strategies/StrategyBase.sol"; contract Utils { address constant dummyAdmin = address(uint160(uint256(keccak256("DummyAdmin"))));