From c00aad7b3b019c58eb4f1360434125c288187406 Mon Sep 17 00:00:00 2001 From: steven <12021290+stevennevins@users.noreply.github.com> Date: Mon, 1 Jul 2024 12:16:15 -0400 Subject: [PATCH] fix: correct index get operator restakable strategies (#280) * fix: add unit tests for ECDSAServiceManager and fix index in getOperatorRestakableStrategies * chore: formatter * chore: formatter --- src/unaudited/ECDSAServiceManagerBase.sol | 24 ++- test/mocks/ECDSAServiceManagerMock.sol | 22 +++ test/mocks/ECDSAStakeRegistryMock.sol | 14 ++ test/unit/ECDSAServiceManager.t.sol | 186 ++++++++++++++++++++++ 4 files changed, 239 insertions(+), 7 deletions(-) create mode 100644 test/mocks/ECDSAServiceManagerMock.sol create mode 100644 test/mocks/ECDSAStakeRegistryMock.sol create mode 100644 test/unit/ECDSAServiceManager.t.sol diff --git a/src/unaudited/ECDSAServiceManagerBase.sol b/src/unaudited/ECDSAServiceManagerBase.sol index 6c1e23db..57326bea 100644 --- a/src/unaudited/ECDSAServiceManagerBase.sol +++ b/src/unaudited/ECDSAServiceManagerBase.sol @@ -13,7 +13,10 @@ import {IRewardsCoordinator} from "eigenlayer-contracts/src/contracts/interfaces import {Quorum} from "../interfaces/IECDSAStakeRegistryEventsAndErrors.sol"; import {ECDSAStakeRegistry} from "../unaudited/ECDSAStakeRegistry.sol"; -abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable { +abstract contract ECDSAServiceManagerBase is + IServiceManager, + OwnableUpgradeable +{ /// @notice Address of the stake registry contract, which manages registration and stake recording. address public immutable stakeRegistry; @@ -185,15 +188,19 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable address(this), rewardsSubmissions[i].amount ); - uint256 allowance = - rewardsSubmissions[i].token.allowance(address(this), rewardsCoordinator); + uint256 allowance = rewardsSubmissions[i].token.allowance( + address(this), + rewardsCoordinator + ); rewardsSubmissions[i].token.approve( rewardsCoordinator, rewardsSubmissions[i].amount + allowance ); } - IRewardsCoordinator(rewardsCoordinator).createAVSRewardsSubmission(rewardsSubmissions); + IRewardsCoordinator(rewardsCoordinator).createAVSRewardsSubmission( + rewardsSubmissions + ); } /** @@ -234,7 +241,6 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable uint256[] memory shares = IDelegationManager(delegationManager) .getOperatorShares(_operator, strategies); - address[] memory activeStrategies = new address[](count); uint256 activeCount; for (uint256 i; i < count; i++) { if (shares[i] > 0) { @@ -244,9 +250,11 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable // Resize the array to fit only the active strategies address[] memory restakedStrategies = new address[](activeCount); + uint256 index; for (uint256 j = 0; j < count; j++) { if (shares[j] > 0) { - restakedStrategies[j] = activeStrategies[j]; + restakedStrategies[index] = address(strategies[j]); + index++; } } @@ -258,7 +266,9 @@ abstract contract ECDSAServiceManagerBase is IServiceManager, OwnableUpgradeable * @param newRewardsInitiator The new rewards initiator address. * @dev Only callable by the owner. */ - function setRewardsInitiator(address newRewardsInitiator) external onlyOwner { + function setRewardsInitiator( + address newRewardsInitiator + ) external onlyOwner { _setRewardsInitiator(newRewardsInitiator); } diff --git a/test/mocks/ECDSAServiceManagerMock.sol b/test/mocks/ECDSAServiceManagerMock.sol new file mode 100644 index 00000000..528270ae --- /dev/null +++ b/test/mocks/ECDSAServiceManagerMock.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.12; + +import "../../src/unaudited/ECDSAServiceManagerBase.sol"; + +contract ECDSAServiceManagerMock is ECDSAServiceManagerBase { + constructor( + address _avsDirectory, + address _stakeRegistry, + address _rewardsCoordinator, + address _delegationManager + ) + ECDSAServiceManagerBase(_avsDirectory, _stakeRegistry, _rewardsCoordinator, _delegationManager) + {} + + function initialize( + address initialOwner, + address rewardsInitiator + ) public virtual initializer { + __ServiceManagerBase_init(initialOwner, rewardsInitiator); + } +} diff --git a/test/mocks/ECDSAStakeRegistryMock.sol b/test/mocks/ECDSAStakeRegistryMock.sol new file mode 100644 index 00000000..7ad6043e --- /dev/null +++ b/test/mocks/ECDSAStakeRegistryMock.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.12; + +import "../../src/unaudited/ECDSAStakeRegistry.sol"; + +/** + * @title Mock for ECDSAStakeRegistry + * @dev This contract is a mock implementation of the ECDSAStakeRegistry for testing purposes. + */ +contract ECDSAStakeRegistryMock is ECDSAStakeRegistry { + + constructor(IDelegationManager _delegationManager) ECDSAStakeRegistry(_delegationManager) { + } +} diff --git a/test/unit/ECDSAServiceManager.t.sol b/test/unit/ECDSAServiceManager.t.sol new file mode 100644 index 00000000..3b533d47 --- /dev/null +++ b/test/unit/ECDSAServiceManager.t.sol @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.12; + +import {Test, console} from "forge-std/Test.sol"; + +import {ISignatureUtils} from "eigenlayer-contracts/src/contracts/interfaces/ISignatureUtils.sol"; +import {IDelegationManager} from "eigenlayer-contracts/src/contracts/interfaces/IDelegationManager.sol"; +import {IRewardsCoordinator} from "eigenlayer-contracts/src/contracts/interfaces/IRewardsCoordinator.sol"; +import {IStrategy} from "eigenlayer-contracts/src/contracts/interfaces/IStrategy.sol"; + +import {ECDSAServiceManagerMock} from "../mocks/ECDSAServiceManagerMock.sol"; +import {ECDSAStakeRegistryMock} from "../mocks/ECDSAStakeRegistryMock.sol"; +import {Quorum, StrategyParams} from "../../src/interfaces/IECDSAStakeRegistryEventsAndErrors.sol"; + +contract MockDelegationManager { + function operatorShares(address, address) external pure returns (uint256) { + return 1000; // Return a dummy value for simplicity + } + + function getOperatorShares( + address, + IStrategy[] memory strategies + ) external pure returns (uint256[] memory) { + uint256[] memory response = new uint256[](strategies.length); + for (uint256 i; i < strategies.length; i++) { + response[i] = 1000; + } + return response; // Return a dummy value for simplicity + } +} + +contract MockAVSDirectory { + function registerOperatorToAVS( + address, + ISignatureUtils.SignatureWithSaltAndExpiry memory + ) external pure {} + + function deregisterOperatorFromAVS(address) external pure {} + + function updateAVSMetadataURI(string memory) external pure {} +} + +contract MockRewardsCoordinator { + function createAVSRewardsSubmission( + IRewardsCoordinator.RewardsSubmission[] calldata + ) external pure {} +} + +contract ECDSAServiceManagerSetup is Test { + MockDelegationManager public mockDelegationManager; + MockAVSDirectory public mockAVSDirectory; + ECDSAStakeRegistryMock public mockStakeRegistry; + MockRewardsCoordinator public mockRewardsCoordinator; + ECDSAServiceManagerMock public serviceManager; + address internal operator1; + address internal operator2; + uint256 internal operator1Pk; + uint256 internal operator2Pk; + + function setUp() public { + mockDelegationManager = new MockDelegationManager(); + mockAVSDirectory = new MockAVSDirectory(); + mockStakeRegistry = new ECDSAStakeRegistryMock( + IDelegationManager(address(mockDelegationManager)) + ); + mockRewardsCoordinator = new MockRewardsCoordinator(); + + serviceManager = new ECDSAServiceManagerMock( + address(mockAVSDirectory), + address(mockStakeRegistry), + address(mockRewardsCoordinator), + address(mockDelegationManager) + ); + + operator1Pk = 1; + operator2Pk = 2; + operator1 = vm.addr(operator1Pk); + operator2 = vm.addr(operator2Pk); + + // Create a quorum + Quorum memory quorum = Quorum({strategies: new StrategyParams[](2)}); + quorum.strategies[0] = StrategyParams({ + strategy: IStrategy(address(420)), + multiplier: 5000 + }); + quorum.strategies[1] = StrategyParams({ + strategy: IStrategy(address(421)), + multiplier: 5000 + }); + address[] memory operators = new address[](0); + + vm.prank(mockStakeRegistry.owner()); + mockStakeRegistry.initialize( + address(serviceManager), + 10_000, // Assuming a threshold weight of 10000 basis points + quorum + ); + ISignatureUtils.SignatureWithSaltAndExpiry memory dummySignature; + + vm.prank(operator1); + mockStakeRegistry.registerOperatorWithSignature( + dummySignature, + operator1 + ); + + vm.prank(operator2); + mockStakeRegistry.registerOperatorWithSignature( + dummySignature, + operator2 + ); + } + + function testRegisterOperatorToAVS() public { + address operator = operator1; + ISignatureUtils.SignatureWithSaltAndExpiry memory signature; + + vm.prank(address(mockStakeRegistry)); + serviceManager.registerOperatorToAVS(operator, signature); + } + + function testDeregisterOperatorFromAVS() public { + address operator = operator1; + + vm.prank(address(mockStakeRegistry)); + serviceManager.deregisterOperatorFromAVS(operator); + } + + function testGetRestakeableStrategies() public { + address[] memory strategies = serviceManager.getRestakeableStrategies(); + } + + function testGetOperatorRestakedStrategies() public { + address operator = operator1; + address[] memory strategies = serviceManager + .getOperatorRestakedStrategies(operator); + } + + function test_Regression_GetOperatorRestakedStrategies_NoShares() public { + address operator = operator1; + IStrategy[] memory strategies = new IStrategy[](2); + strategies[0] = IStrategy(address(420)); + strategies[1] = IStrategy(address(421)); + + uint256[] memory shares = new uint256[](2); + shares[0] = 0; + shares[1] = 1; + + vm.mockCall( + address(mockDelegationManager), + abi.encodeCall( + IDelegationManager.getOperatorShares, + (operator, strategies) + ), + abi.encode(shares) + ); + + address[] memory restakedStrategies = serviceManager + .getOperatorRestakedStrategies(operator); + assertEq( + restakedStrategies.length, + 1, + "Expected no restaked strategies" + ); + } + + function testUpdateAVSMetadataURI() public { + string memory newURI = "https://new-metadata-uri.com"; + + vm.prank(mockStakeRegistry.owner()); + serviceManager.updateAVSMetadataURI(newURI); + } + + function testCreateAVSRewardsSubmission() public { + IRewardsCoordinator.RewardsSubmission[] memory submissions; + + vm.prank(serviceManager.rewardsInitiator()); + serviceManager.createAVSRewardsSubmission(submissions); + } + + function testSetRewardsInitiator() public { + address newInitiator = address(0x123); + + vm.prank(mockStakeRegistry.owner()); + serviceManager.setRewardsInitiator(newInitiator); + } +}