diff --git a/contracts/PoolSelector.sol b/contracts/PoolSelector.sol index d2054637..ecbb4879 100644 --- a/contracts/PoolSelector.sol +++ b/contracts/PoolSelector.sol @@ -108,12 +108,15 @@ contract PoolSelector is IPoolSelector, AccessControlUpgradeable { /** * @notice update the target weights of existing pools - * @dev only `Manager` can call, + * @dev only authorised callers can call, * @param _poolTargets new target weights of pools * `_poolTargets` array provide pool target in the same order of poolIDs that are stored in poolIdArray of poolUtils */ function updatePoolWeights(uint256[] calldata _poolTargets) external { - UtilLib.onlyManagerRole(msg.sender, staderConfig); + if (!staderConfig.isAllowedToCall(msg.sender, "updatePoolWeights(uint256[])")) { + revert AccessDenied(msg.sender); + } + uint8[] memory poolIdArray = IPoolUtils(staderConfig.getPoolUtils()).getPoolIdArray(); uint256 poolCount = poolIdArray.length; uint256 poolTargetLength = _poolTargets.length; diff --git a/contracts/PoolUtils.sol b/contracts/PoolUtils.sol index 38a928e4..f5ede32d 100644 --- a/contracts/PoolUtils.sol +++ b/contracts/PoolUtils.sol @@ -70,7 +70,10 @@ contract PoolUtils is IPoolUtils, AccessControlUpgradeable { * @dev emit an event containing validator pubkey for offchain to exit the validator */ function processValidatorExitList(bytes[] calldata _pubkeys) external override { - UtilLib.onlyOperatorRole(msg.sender, staderConfig); + if (!staderConfig.isAllowedToCall(msg.sender, "processValidatorExitList(bytes[])")) { + revert AccessDenied(msg.sender); + } + uint256 exitValidatorCount = _pubkeys.length; for (uint256 i; i < exitValidatorCount; ) { emit ExitValidator(_pubkeys[i]); diff --git a/contracts/StaderConfig.sol b/contracts/StaderConfig.sol index 24af1f35..6bd19121 100644 --- a/contracts/StaderConfig.sol +++ b/contracts/StaderConfig.sol @@ -297,6 +297,27 @@ contract StaderConfig is IStaderConfig, AccessControlUpgradeable { setContract(SD_INCENTIVE_CONTROLLER, _sdIncentiveController); } + // Access Control + function giveCallPermission( + address contractAddress, + string calldata functionSig, + address accountToPermit + ) external override onlyRole(MANAGER) { + bytes32 role = keccak256(abi.encodePacked(contractAddress, functionSig)); + _grantRole(role, accountToPermit); + emit PermissionGranted(accountToPermit, contractAddress, functionSig); + } + + function revokeCallPermission( + address contractAddress, + string calldata functionSig, + address accountToRevoke + ) external override onlyRole(MANAGER) { + bytes32 role = keccak256(abi.encodePacked(contractAddress, functionSig)); + _revokeRole(role, accountToRevoke); + emit PermissionRevoked(accountToRevoke, contractAddress, functionSig); + } + //Constants Getters function getStakedEthPerNode() external view override returns (uint256) { @@ -537,6 +558,11 @@ contract StaderConfig is IStaderConfig, AccessControlUpgradeable { return hasRole(OPERATOR, account); } + function isAllowedToCall(address account, string calldata functionSig) external view override returns (bool) { + bytes32 role = keccak256(abi.encodePacked(msg.sender, functionSig)); + return hasRole(role, account); + } + function verifyDepositAndWithdrawLimits() internal view { if ( !(variablesMap[MIN_DEPOSIT_AMOUNT] != 0 && diff --git a/contracts/interfaces/IPoolSelector.sol b/contracts/interfaces/IPoolSelector.sol index aea1b49e..ab93b860 100644 --- a/contracts/interfaces/IPoolSelector.sol +++ b/contracts/interfaces/IPoolSelector.sol @@ -6,6 +6,7 @@ interface IPoolSelector { error InvalidTargetWeight(); error InvalidNewTargetInput(); error InvalidSumOfPoolWeights(); + error AccessDenied(address account); // Events diff --git a/contracts/interfaces/IPoolUtils.sol b/contracts/interfaces/IPoolUtils.sol index 35d73bd1..99ff4fcd 100644 --- a/contracts/interfaces/IPoolUtils.sol +++ b/contracts/interfaces/IPoolUtils.sol @@ -14,6 +14,7 @@ interface IPoolUtils { error OperatorIsNotOnboarded(); error InvalidLengthOfSignature(); error ExistingOrMismatchingPoolId(); + error AccessDenied(address account); // Events event PoolAdded(uint8 indexed poolId, address poolAddress); diff --git a/contracts/interfaces/IStaderConfig.sol b/contracts/interfaces/IStaderConfig.sol index f8f9854a..cf49f219 100644 --- a/contracts/interfaces/IStaderConfig.sol +++ b/contracts/interfaces/IStaderConfig.sol @@ -16,6 +16,8 @@ interface IStaderConfig { event SetAccount(bytes32 key, address newAddress); event SetContract(bytes32 key, address newAddress); event SetToken(bytes32 key, address newAddress); + event PermissionGranted(address indexed accountToPermit, address indexed contractAddress, string functionSig); + event PermissionRevoked(address indexed accountToRevoke, address indexed contractAddress, string functionSig); //Contracts function POOL_UTILS() external view returns (bytes32); @@ -171,4 +173,14 @@ interface IStaderConfig { function onlyManagerRole(address account) external view returns (bool); function onlyOperatorRole(address account) external view returns (bool); + + function isAllowedToCall(address account, string calldata functionSig) external view returns (bool); + + function giveCallPermission(address contractAddress, string calldata functionSig, address accountToPermit) external; + + function revokeCallPermission( + address contractAddress, + string calldata functionSig, + address accountToRevoke + ) external; } diff --git a/test/foundry_tests/PoolSelector.t.sol b/test/foundry_tests/PoolSelector.t.sol index baf7ef39..101600e8 100644 --- a/test/foundry_tests/PoolSelector.t.sol +++ b/test/foundry_tests/PoolSelector.t.sol @@ -16,6 +16,8 @@ contract PoolSelectorTest is Test { address staderAdmin; address staderManager; address operator; + address configurator; + address naiveAddress; address staderStakePoolManager; @@ -28,6 +30,8 @@ contract PoolSelectorTest is Test { staderAdmin = vm.addr(100); staderManager = vm.addr(101); operator = vm.addr(102); + configurator = vm.addr(116); + naiveAddress = vm.addr(117); staderStakePoolManager = vm.addr(110); address ethDepositAddr = vm.addr(103); @@ -60,6 +64,9 @@ contract PoolSelectorTest is Test { staderConfig.grantRole(staderConfig.MANAGER(), staderManager); staderConfig.grantRole(staderConfig.OPERATOR(), operator); vm.stopPrank(); + + vm.prank(staderManager); + staderConfig.giveCallPermission(address(poolSelector), "updatePoolWeights(uint256[])", configurator); } function test_JustToIncreaseCoverage() public { @@ -95,10 +102,11 @@ contract PoolSelectorTest is Test { invalidSizePoolWeight[1] = 4000; invalidSizePoolWeight[2] = 4000; - vm.expectRevert(UtilLib.CallerNotManager.selector); + vm.prank(naiveAddress); + vm.expectRevert(abi.encodeWithSignature("AccessDenied(address)", naiveAddress)); poolSelector.updatePoolWeights(poolWeight); - vm.startPrank(staderManager); + vm.startPrank(configurator); vm.expectRevert(IPoolSelector.InvalidNewTargetInput.selector); poolSelector.updatePoolWeights(invalidSizePoolWeight); @@ -112,7 +120,7 @@ contract PoolSelectorTest is Test { uint256[] memory poolWeight = new uint256[](2); poolWeight[0] = 7000; poolWeight[1] = 3000; - vm.prank(staderManager); + vm.prank(configurator); poolSelector.updatePoolWeights(poolWeight); vm.prank(operator); poolSelector.updatePoolAllocationMaxSize(1000); diff --git a/test/foundry_tests/PoolUtils.t.sol b/test/foundry_tests/PoolUtils.t.sol index e7f10ddc..cf444381 100644 --- a/test/foundry_tests/PoolUtils.t.sol +++ b/test/foundry_tests/PoolUtils.t.sol @@ -17,6 +17,8 @@ contract PoolUtilsTest is Test { address staderAdmin; address staderManager; address operator; + address configurator; + address naiveAddress; PoolUtils poolUtils; StaderConfig staderConfig; @@ -31,6 +33,8 @@ contract PoolUtilsTest is Test { staderAdmin = vm.addr(100); staderManager = vm.addr(101); operator = vm.addr(102); + configurator = vm.addr(114); + naiveAddress = vm.addr(117); address ethDepositAddr = vm.addr(103); nodeRegistry = new NodeRegistryMock(operator); @@ -60,6 +64,9 @@ contract PoolUtilsTest is Test { staderConfig.grantRole(staderConfig.MANAGER(), staderManager); staderConfig.grantRole(staderConfig.OPERATOR(), operator); vm.stopPrank(); + + vm.prank(staderManager); + staderConfig.giveCallPermission(address(poolUtils), "processValidatorExitList(bytes[])", configurator); } function test_JustToIncreaseCoverage() public { @@ -134,10 +141,11 @@ contract PoolUtilsTest is Test { pubkey[0] = "0x8faa339ba46c649885ea0fc9c34d32f9d99c5bde336750"; pubkey[1] = "0x8faa339ba46c649885ea0fc9c34d32f9d99c5bde336750"; - vm.expectRevert(UtilLib.CallerNotOperator.selector); + vm.prank(naiveAddress); + vm.expectRevert(abi.encodeWithSignature("AccessDenied(address)", naiveAddress)); poolUtils.processValidatorExitList(pubkey); - vm.prank(operator); + vm.prank(configurator); poolUtils.processValidatorExitList(pubkey); }