diff --git a/src/libraries/types/Roles.sol b/src/libraries/types/Roles.sol index 7dcf13f6e..49c7dea95 100644 --- a/src/libraries/types/Roles.sol +++ b/src/libraries/types/Roles.sol @@ -13,4 +13,5 @@ library Roles { uint64 public constant HUB_CONFIGURATOR_ROLE = 4; uint64 public constant SPOKE_CONFIGURATOR_ROLE = 5; uint64 public constant DEFICIT_ELIMINATOR_ROLE = 6; + uint64 public constant PROXY_ADMIN_ROLE = 7; } diff --git a/tests/unit/Spoke/Spoke.Upgradeable.t.sol b/tests/unit/Spoke/Spoke.Upgradeable.t.sol index 6a1524722..525ae36dd 100644 --- a/tests/unit/Spoke/Spoke.Upgradeable.t.sol +++ b/tests/unit/Spoke/Spoke.Upgradeable.t.sol @@ -207,6 +207,66 @@ contract SpokeUpgradeableTest is SpokeBase { ); } + function test_proxy_accessManager_ownershipRole() public { + address proxyAdmin1 = makeAddr('proxyAdmin1'); + address proxyAdmin2 = makeAddr('proxyAdmin2'); + + uint64 initialRevision = 1; + ISpokeInstance spokeImpl = _deployMockSpokeInstance(initialRevision); + ITransparentUpgradeableProxy spokeProxy = ITransparentUpgradeableProxy( + address( + new TransparentUpgradeableProxy( + address(spokeImpl), + address(accessManager), + abi.encodeCall(ISpokeInstance.initialize, address(accessManager)) + ) + ) + ); + + ProxyAdmin proxyAdmin = ProxyAdmin(_getProxyAdminAddress(address(spokeProxy))); + + vm.startPrank(ADMIN); + accessManager.labelRole(Roles.PROXY_ADMIN_ROLE, 'PROXY_ADMIN_ROLE'); + accessManager.setGrantDelay(Roles.PROXY_ADMIN_ROLE, 0); + + accessManager.grantRole(Roles.PROXY_ADMIN_ROLE, proxyAdmin1, 0); + accessManager.grantRole(Roles.PROXY_ADMIN_ROLE, proxyAdmin2, 0); + + bytes4[] memory selectors = new bytes4[](1); + selectors[0] = ProxyAdmin.upgradeAndCall.selector; + accessManager.setTargetFunctionRole(address(proxyAdmin), selectors, Roles.PROXY_ADMIN_ROLE); + vm.stopPrank(); + + ISpokeInstance spokeImpl2 = _deployMockSpokeInstance(initialRevision + 1); + ISpokeInstance spokeImpl3 = _deployMockSpokeInstance(initialRevision + 2); + + vm.prank(proxyAdmin1); + accessManager.execute( + address(proxyAdmin), + abi.encodeWithSelector( + ProxyAdmin.upgradeAndCall.selector, + spokeProxy, + address(spokeImpl2), + _getInitializeCalldata(address(accessManager)) + ) + ); + + assertEq(_getImplementationAddress(address(spokeProxy)), address(spokeImpl2)); + + vm.prank(proxyAdmin2); + accessManager.execute( + address(proxyAdmin), + abi.encodeWithSelector( + ProxyAdmin.upgradeAndCall.selector, + spokeProxy, + address(spokeImpl3), + _getInitializeCalldata(address(accessManager)) + ) + ); + + assertEq(_getImplementationAddress(address(spokeProxy)), address(spokeImpl3)); + } + function _getInitializeCalldata(address manager) internal pure returns (bytes memory) { return abi.encodeCall(ISpokeInstance.initialize, manager); }