From 698121ae893997d896debf59ed02646529bf7783 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Tue, 30 Apr 2024 09:56:11 +0200 Subject: [PATCH 01/10] Add custom external call to ReOp --- script/SetupAccess.s.sol | 3 +- src/Errors.sol | 6 ++++ src/PufferModuleManager.sol | 19 +++++++++- src/RestakingOperator.sol | 13 +++++++ src/interface/IPufferModuleManager.sol | 16 +++++++++ src/interface/IRestakingOperator.sol | 9 +++++ test/unit/PufferProtocol.t.sol | 49 ++++++++++++++------------ 7 files changed, 90 insertions(+), 25 deletions(-) diff --git a/script/SetupAccess.s.sol b/script/SetupAccess.s.sol index 3211ad49..87ce9f13 100644 --- a/script/SetupAccess.s.sol +++ b/script/SetupAccess.s.sol @@ -125,7 +125,7 @@ contract SetupAccess is BaseScript { bytes[] memory calldatas = new bytes[](3); // Dao selectors - bytes4[] memory selectors = new bytes4[](11); + bytes4[] memory selectors = new bytes4[](12); selectors[0] = PufferModuleManager.createNewRestakingOperator.selector; selectors[1] = PufferModuleManager.callModifyOperatorDetails.selector; selectors[2] = PufferModuleManager.callOptIntoSlashing.selector; @@ -137,6 +137,7 @@ contract SetupAccess is BaseScript { selectors[8] = PufferModuleManager.callRegisterOperatorToAVSWithChurn.selector; selectors[9] = PufferModuleManager.callDeregisterOperatorFromAVS.selector; selectors[10] = PufferModuleManager.callUpdateOperatorAVSSocket.selector; + selectors[11] = PufferModuleManager.customExternalCall.selector; calldatas[0] = abi.encodeWithSelector( AccessManager.setTargetFunctionRole.selector, pufferDeployment.moduleManager, selectors, ROLE_ID_DAO diff --git a/src/Errors.sol b/src/Errors.sol index 1977ddf2..ed369f52 100644 --- a/src/Errors.sol +++ b/src/Errors.sol @@ -12,3 +12,9 @@ error Unauthorized(); * @dev Signature "0xe6c4247b" */ error InvalidAddress(); + +/** + * @notice Thrown if the custom call failed + * @dev Signature "0x5515e1c7" + */ +error CustomCallFailed(address target, bytes returnData); diff --git a/src/PufferModuleManager.sol b/src/PufferModuleManager.sol index 66729d94..0e314568 100644 --- a/src/PufferModuleManager.sol +++ b/src/PufferModuleManager.sol @@ -3,7 +3,7 @@ pragma solidity >=0.8.0 <0.9.0; import { IPufferModule } from "puffer/interface/IPufferModule.sol"; import { IPufferProtocol } from "puffer/interface/IPufferProtocol.sol"; -import { Unauthorized } from "puffer/Errors.sol"; +import { Unauthorized, CustomCallFailed } from "puffer/Errors.sol"; import { IRestakingOperator } from "puffer/interface/IRestakingOperator.sol"; import { IPufferProtocol } from "puffer/interface/IPufferProtocol.sol"; import { PufferModule } from "puffer/PufferModule.sol"; @@ -343,6 +343,23 @@ contract PufferModuleManager is IPufferModuleManager, AccessManagedUpgradeable, }); } + /** + * @inheritdoc IPufferModuleManager + * @dev Restricted to the DAO + */ + function customExternalCall(IRestakingOperator restakingOperator, address target, bytes calldata customCalldata) + external + virtual + restricted + { + (bool success, bytes memory response) = restakingOperator.customCalldataCall(target, customCalldata); + if (!success) { + revert CustomCallFailed(address(restakingOperator), response); + } + + emit CustomCallSucceed(address(restakingOperator), target, customCalldata, response); + } + /** * @inheritdoc IPufferModuleManager * @dev Restricted to the DAO diff --git a/src/RestakingOperator.sol b/src/RestakingOperator.sol index 8bd61fe9..63f1b29d 100644 --- a/src/RestakingOperator.sol +++ b/src/RestakingOperator.sol @@ -171,6 +171,19 @@ contract RestakingOperator is IRestakingOperator, IERC1271, Initializable, Acces }); } + /** + * @inheritdoc IRestakingOperator + * @dev Restricted to the PufferModuleManager + */ + function customCalldataCall(address target, bytes calldata customCalldata) + external + virtual + onlyPufferModuleManager + returns (bool success, bytes memory response) + { + return target.call(customCalldata); + } + /** * @inheritdoc IRestakingOperator * @dev Restricted to the PufferModuleManager diff --git a/src/interface/IPufferModuleManager.sol b/src/interface/IPufferModuleManager.sol index fc92ee15..8d5eab27 100644 --- a/src/interface/IPufferModuleManager.sol +++ b/src/interface/IPufferModuleManager.sol @@ -20,6 +20,12 @@ interface IPufferModuleManager { */ error ForbiddenModuleName(); + /** + * @notice Emitted when the Custom Call from the restakingOperator is successful + * @dev Signature "0x80b240e4b7a31d61bdee28b97592a7c0ad486cb27d11ee5c6b90530db4e949ff" + */ + event CustomCallSucceed(address indexed restakingOperator, address target, bytes customCalldata, bytes response); + /** * @notice Emitted when a Restaking Operator is opted into a slasher * @param restakingOperator is the address of the restaking operator @@ -401,4 +407,14 @@ interface IPufferModuleManager { address avsRegistryCoordinator, string memory socket ) external; + + /** + * @notice Calls the `target` contract with `customCalldata` from the Restaking Operator contract + * @param restakingOperator is the Restaking Operator contract + * @param target is the address of the target contract that ReOp will call + * @param customCalldata is the calldata to be passed to the target contract + * @dev Restricted to the DAO + */ + function customExternalCall(IRestakingOperator restakingOperator, address target, bytes calldata customCalldata) + external; } diff --git a/src/interface/IRestakingOperator.sol b/src/interface/IRestakingOperator.sol index 236dd101..02db9523 100644 --- a/src/interface/IRestakingOperator.sol +++ b/src/interface/IRestakingOperator.sol @@ -93,6 +93,15 @@ interface IRestakingOperator { ISignatureUtils.SignatureWithSaltAndExpiry calldata operatorSignature ) external; + /** + * @notice Does a custom call to `target` with `customCalldata` + * @return success + * @return response + */ + function customCalldataCall(address target, bytes calldata customCalldata) + external + returns (bool success, bytes memory response); + /** * @notice Deregisters the caller from one or more quorums * @param avsRegistryCoordinator the avs registry coordinator address diff --git a/test/unit/PufferProtocol.t.sol b/test/unit/PufferProtocol.t.sol index 7baf8f4a..f0fec86d 100644 --- a/test/unit/PufferProtocol.t.sol +++ b/test/unit/PufferProtocol.t.sol @@ -31,6 +31,7 @@ contract PufferProtocolTest is TestHelper { bytes32 constant EIGEN_DA = bytes32("EIGEN_DA"); bytes32 constant CRAZY_GAINS = bytes32("CRAZY_GAINS"); + bytes32 constant DEFAULT_DEPOSIT_ROOT = bytes32("depositRoot"); Permit emptyPermit; @@ -144,7 +145,7 @@ contract PufferProtocolTest is TestHelper { vm.expectEmit(true, true, true, true); emit SuccessfullyProvisioned(_getPubKey(bytes32("bob")), 1, PUFFER_MODULE_0); - pufferProtocol.provisionNode(signatures, _validatorSignature(), bytes32(0)); + pufferProtocol.provisionNode(signatures, _validatorSignature(), DEFAULT_DEPOSIT_ROOT); moduleSelectionIndex = pufferProtocol.getModuleSelectIndex(); assertEq(moduleSelectionIndex, 1, "module idx changed"); } @@ -315,7 +316,7 @@ contract PufferProtocolTest is TestHelper { _getGuardianSignatures(hex"0000000000000000000000000000000000000000000000000000000000000000"); vm.expectRevert(); // panic - pufferProtocol.provisionNode(signatures, _validatorSignature(), bytes32(0)); + pufferProtocol.provisionNode(signatures, _validatorSignature(), DEFAULT_DEPOSIT_ROOT); } // If the deposit root is not bytes(0), it must match match the one returned from the beacon contract @@ -329,7 +330,7 @@ contract PufferProtocolTest is TestHelper { pufferProtocol.provisionNode(guardianSignatures, validatorSignature, bytes32("badDepositRoot")); // "depositRoot" is hardcoded in the mock // now it works - pufferProtocol.provisionNode(guardianSignatures, validatorSignature, bytes32("depositRoot")); + pufferProtocol.provisionNode(guardianSignatures, validatorSignature, DEFAULT_DEPOSIT_ROOT); } function test_register_multiple_validators_and_skipProvisioning(bytes32 alicePubKeyPart, bytes32 bobPubKeyPart) @@ -371,14 +372,14 @@ contract PufferProtocolTest is TestHelper { // 1. provision zero key vm.expectEmit(true, true, true, true); emit SuccessfullyProvisioned(zeroPubKey, 0, PUFFER_MODULE_0); - pufferProtocol.provisionNode(signatures, _validatorSignature(), bytes32(0)); + pufferProtocol.provisionNode(signatures, _validatorSignature(), DEFAULT_DEPOSIT_ROOT); bytes[] memory bobSignatures = _getGuardianSignatures(bobPubKey); // Provision Bob that is not zero pubKey vm.expectEmit(true, true, true, true); emit SuccessfullyProvisioned(bobPubKey, 1, PUFFER_MODULE_0); - pufferProtocol.provisionNode(bobSignatures, _validatorSignature(), bytes32(0)); + pufferProtocol.provisionNode(bobSignatures, _validatorSignature(), DEFAULT_DEPOSIT_ROOT); Validator memory bobValidator = pufferProtocol.getValidatorInfo(PUFFER_MODULE_0, 1); @@ -389,7 +390,7 @@ contract PufferProtocolTest is TestHelper { signatures = _getGuardianSignatures(zeroPubKey); emit SuccessfullyProvisioned(zeroPubKey, 3, PUFFER_MODULE_0); - pufferProtocol.provisionNode(signatures, _validatorSignature(), bytes32(0)); + pufferProtocol.provisionNode(signatures, _validatorSignature(), DEFAULT_DEPOSIT_ROOT); // Get validators Validator[] memory registeredValidators = pufferProtocol.getValidators(PUFFER_MODULE_0); @@ -440,7 +441,7 @@ contract PufferProtocolTest is TestHelper { // Provision Bob that is not zero pubKey vm.expectEmit(true, true, true, true); emit SuccessfullyProvisioned(_getPubKey(bytes32("bob")), 0, PUFFER_MODULE_0); - pufferProtocol.provisionNode(signatures, _validatorSignature(), bytes32(0)); + pufferProtocol.provisionNode(signatures, _validatorSignature(), DEFAULT_DEPOSIT_ROOT); (nextModule, nextId) = pufferProtocol.getNextValidatorToProvision(); @@ -452,7 +453,7 @@ contract PufferProtocolTest is TestHelper { vm.expectEmit(true, true, true, true); emit SuccessfullyProvisioned(_getPubKey(bytes32("benjamin")), 0, EIGEN_DA); - pufferProtocol.provisionNode(signatures, _validatorSignature(), bytes32(0)); + pufferProtocol.provisionNode(signatures, _validatorSignature(), DEFAULT_DEPOSIT_ROOT); (nextModule, nextId) = pufferProtocol.getNextValidatorToProvision(); @@ -474,12 +475,12 @@ contract PufferProtocolTest is TestHelper { // Provisioning of rocky should fail, because jason is next in line signatures = _getGuardianSignatures(_getPubKey(bytes32("rocky"))); vm.expectRevert(Unauthorized.selector); - pufferProtocol.provisionNode(signatures, _validatorSignature(), bytes32(0)); + pufferProtocol.provisionNode(signatures, _validatorSignature(), DEFAULT_DEPOSIT_ROOT); signatures = _getGuardianSignatures(_getPubKey(bytes32("jason"))); // Provision Jason - pufferProtocol.provisionNode(signatures, _validatorSignature(), bytes32(0)); + pufferProtocol.provisionNode(signatures, _validatorSignature(), DEFAULT_DEPOSIT_ROOT); (nextModule, nextId) = pufferProtocol.getNextValidatorToProvision(); @@ -488,7 +489,7 @@ contract PufferProtocolTest is TestHelper { // Rocky is now in line assertTrue(nextModule == CRAZY_GAINS, "module selection"); assertTrue(nextId == 0, "module id"); - pufferProtocol.provisionNode(signatures, _validatorSignature(), bytes32(0)); + pufferProtocol.provisionNode(signatures, _validatorSignature(), DEFAULT_DEPOSIT_ROOT); (nextModule, nextId) = pufferProtocol.getNextValidatorToProvision(); @@ -503,7 +504,7 @@ contract PufferProtocolTest is TestHelper { vm.expectEmit(true, true, true, true); emit SuccessfullyProvisioned(_getPubKey(bytes32("alice")), 1, PUFFER_MODULE_0); - pufferProtocol.provisionNode(signatures, _validatorSignature(), bytes32(0)); + pufferProtocol.provisionNode(signatures, _validatorSignature(), DEFAULT_DEPOSIT_ROOT); } function test_create_puffer_module() public { @@ -765,7 +766,7 @@ contract PufferProtocolTest is TestHelper { bytes[] memory guardianSignatures = _getGuardianSignatures(_getPubKey(bytes32("alice"))); // Register and provision Alice // Alice may be an active validator or it can be exited, doesn't matter - pufferProtocol.provisionNode(guardianSignatures, _validatorSignature(), bytes32(0)); + pufferProtocol.provisionNode(guardianSignatures, _validatorSignature(), DEFAULT_DEPOSIT_ROOT); // Register another validator with using the same data _registerValidatorKey(bytes32("alice"), PUFFER_MODULE_0); @@ -773,7 +774,7 @@ contract PufferProtocolTest is TestHelper { // Try to provision it with the original message (replay attack) // It should revert vm.expectRevert(Unauthorized.selector); - pufferProtocol.provisionNode(guardianSignatures, _validatorSignature(), bytes32(0)); + pufferProtocol.provisionNode(guardianSignatures, _validatorSignature(), DEFAULT_DEPOSIT_ROOT); } function test_validator_limit_per_module() external { @@ -823,7 +824,7 @@ contract PufferProtocolTest is TestHelper { vm.warp(startTimestamp); pufferProtocol.provisionNode( - _getGuardianSignatures(_getPubKey(bytes32("alice"))), _validatorSignature(), bytes32(0) + _getGuardianSignatures(_getPubKey(bytes32("alice"))), _validatorSignature(), DEFAULT_DEPOSIT_ROOT ); // Didn't claim the bond yet @@ -1474,7 +1475,7 @@ contract PufferProtocolTest is TestHelper { uint256 startTimestamp = 1707411226; vm.warp(startTimestamp); pufferProtocol.provisionNode( - _getGuardianSignatures(_getPubKey(bytes32("alice"))), _validatorSignature(), bytes32(0) + _getGuardianSignatures(_getPubKey(bytes32("alice"))), _validatorSignature(), DEFAULT_DEPOSIT_ROOT ); // Give funds to modules @@ -1528,7 +1529,7 @@ contract PufferProtocolTest is TestHelper { uint256 startTimestamp = 1707411226; vm.warp(startTimestamp); pufferProtocol.provisionNode( - _getGuardianSignatures(_getPubKey(bytes32("alice"))), _validatorSignature(), bytes32(0) + _getGuardianSignatures(_getPubKey(bytes32("alice"))), _validatorSignature(), DEFAULT_DEPOSIT_ROOT ); vm.deal(NoRestakingModule, 200 ether); @@ -1580,7 +1581,7 @@ contract PufferProtocolTest is TestHelper { uint256 startTimestamp = 1707411226; vm.warp(startTimestamp); pufferProtocol.provisionNode( - _getGuardianSignatures(_getPubKey(bytes32("alice"))), _validatorSignature(), bytes32(0) + _getGuardianSignatures(_getPubKey(bytes32("alice"))), _validatorSignature(), DEFAULT_DEPOSIT_ROOT ); vm.deal(NoRestakingModule, 200 ether); @@ -1640,7 +1641,7 @@ contract PufferProtocolTest is TestHelper { uint256 startTimestamp = 1707411226; vm.warp(startTimestamp); pufferProtocol.provisionNode( - _getGuardianSignatures(_getPubKey(bytes32("alice"))), _validatorSignature(), bytes32(0) + _getGuardianSignatures(_getPubKey(bytes32("alice"))), _validatorSignature(), DEFAULT_DEPOSIT_ROOT ); vm.deal(NoRestakingModule, 200 ether); @@ -1692,7 +1693,7 @@ contract PufferProtocolTest is TestHelper { uint256 startTimestamp = 1707411226; vm.warp(startTimestamp); pufferProtocol.provisionNode( - _getGuardianSignatures(_getPubKey(bytes32("alice"))), _validatorSignature(), bytes32(0) + _getGuardianSignatures(_getPubKey(bytes32("alice"))), _validatorSignature(), DEFAULT_DEPOSIT_ROOT ); vm.deal(NoRestakingModule, 200 ether); @@ -1735,7 +1736,7 @@ contract PufferProtocolTest is TestHelper { vm.stopPrank(); pufferProtocol.provisionNode( - _getGuardianSignatures(_getPubKey(bytes32("alice"))), _validatorSignature(), bytes32(0) + _getGuardianSignatures(_getPubKey(bytes32("alice"))), _validatorSignature(), DEFAULT_DEPOSIT_ROOT ); // Alice exited after 1 day @@ -1765,7 +1766,7 @@ contract PufferProtocolTest is TestHelper { vm.stopPrank(); pufferProtocol.provisionNode( - _getGuardianSignatures(_getPubKey(bytes32("alice"))), _validatorSignature(), bytes32(0) + _getGuardianSignatures(_getPubKey(bytes32("alice"))), _validatorSignature(), DEFAULT_DEPOSIT_ROOT ); vm.startPrank(DAO); @@ -2040,7 +2041,9 @@ contract PufferProtocolTest is TestHelper { _registerValidatorKey(pubKeyPart, moduleName); vm.stopPrank(); - pufferProtocol.provisionNode(_getGuardianSignatures(_getPubKey(pubKeyPart)), _validatorSignature(), bytes32(0)); + pufferProtocol.provisionNode( + _getGuardianSignatures(_getPubKey(pubKeyPart)), _validatorSignature(), DEFAULT_DEPOSIT_ROOT + ); } /** From 92615fa3612815044466a48a2f0e1db788c3b2ef Mon Sep 17 00:00:00 2001 From: Benjamin Date: Tue, 30 Apr 2024 16:08:05 +0200 Subject: [PATCH 02/10] typo --- src/PufferModuleManager.sol | 2 +- src/interface/IPufferModuleManager.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PufferModuleManager.sol b/src/PufferModuleManager.sol index 0e314568..ec9d667e 100644 --- a/src/PufferModuleManager.sol +++ b/src/PufferModuleManager.sol @@ -357,7 +357,7 @@ contract PufferModuleManager is IPufferModuleManager, AccessManagedUpgradeable, revert CustomCallFailed(address(restakingOperator), response); } - emit CustomCallSucceed(address(restakingOperator), target, customCalldata, response); + emit CustomCallSucceeded(address(restakingOperator), target, customCalldata, response); } /** diff --git a/src/interface/IPufferModuleManager.sol b/src/interface/IPufferModuleManager.sol index 8d5eab27..d1fa532e 100644 --- a/src/interface/IPufferModuleManager.sol +++ b/src/interface/IPufferModuleManager.sol @@ -24,7 +24,7 @@ interface IPufferModuleManager { * @notice Emitted when the Custom Call from the restakingOperator is successful * @dev Signature "0x80b240e4b7a31d61bdee28b97592a7c0ad486cb27d11ee5c6b90530db4e949ff" */ - event CustomCallSucceed(address indexed restakingOperator, address target, bytes customCalldata, bytes response); + event CustomCallSucceeded(address indexed restakingOperator, address target, bytes customCalldata, bytes response); /** * @notice Emitted when a Restaking Operator is opted into a slasher From c52d7156b960507795c53009ceb4a491cbbdbed0 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Wed, 8 May 2024 17:52:44 +0200 Subject: [PATCH 03/10] add avs contracts registry --- .../GenerateAccessManagerCalldata1.s.sol | 54 +++++++++++++++++++ script/DeployProtocolToMainnet.s.sol | 9 +++- script/DeployPuffer.s.sol | 8 ++- script/DeployPufferModuleImplementation.s.sol | 1 - ...loyPufferModuleManagerImplementation.s.sol | 6 ++- script/DeploymentStructs.sol | 1 + script/SetupAccess.s.sol | 6 +++ src/AVSContractsRegistry.sol | 34 ++++++++++++ src/PufferModuleManager.sol | 19 ++++++- ...anagerHoleskyTestnetTest.integration.t.sol | 4 +- test/helpers/TestHelper.sol | 3 ++ test/unit/PufferModuleManager.t.sol | 40 ++++++++++++++ 12 files changed, 179 insertions(+), 6 deletions(-) create mode 100644 script/AccessManagerMigrations/GenerateAccessManagerCalldata1.s.sol create mode 100644 src/AVSContractsRegistry.sol diff --git a/script/AccessManagerMigrations/GenerateAccessManagerCalldata1.s.sol b/script/AccessManagerMigrations/GenerateAccessManagerCalldata1.s.sol new file mode 100644 index 00000000..6b8d3430 --- /dev/null +++ b/script/AccessManagerMigrations/GenerateAccessManagerCalldata1.s.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +import { Script } from "forge-std/Script.sol"; +import { AccessManager } from "openzeppelin/access/manager/AccessManager.sol"; +import { Multicall } from "openzeppelin/utils/Multicall.sol"; +import { console } from "forge-std/console.sol"; +import { AVSContractsRegistry } from "../../src/AVSContractsRegistry.sol"; +import { ROLE_ID_AVS_COORDINATOR_WHITELISTER, ROLE_ID_DAO } from "pufETHScript/Roles.sol"; + +/** + * @title GenerateAccessManagerCalldata1 + * @author Puffer Finance + * @notice Generates the AccessManager call data to setup the public access + * The returned calldata is queued and executed by the Operations Multisig + * 1. timelock.queueTransaction(address(accessManager), encodedMulticall, 1) + * 2. ... 7 days later ... + * 3. timelock.executeTransaction(address(accessManager), encodedMulticall, 1) + */ +contract GenerateAccessManagerCalldata1 is Script { + function run(address avsContractsRegistry, address whitelister) public pure returns (bytes memory) { + bytes[] memory calldatas = new bytes[](3); + + bytes4[] memory whitelisterSelectors = new bytes4[](1); + whitelisterSelectors[0] = AVSContractsRegistry.setAvsRegistryCoordinator.selector; + + calldatas[0] = abi.encodeWithSelector( + AccessManager.setTargetFunctionRole.selector, + avsContractsRegistry, + whitelisterSelectors, + ROLE_ID_AVS_COORDINATOR_WHITELISTER + ); + + // Whitelister has 1 day timelock to add new coordinators + calldatas[1] = abi.encodeWithSelector( + AccessManager.grantRole.selector, + ROLE_ID_AVS_COORDINATOR_WHITELISTER, + whitelister, + 1 days // 1 day timelock + ); + + // The role guardian can cancel + calldatas[2] = abi.encodeWithSelector( + AccessManager.setRoleGuardian.selector, ROLE_ID_AVS_COORDINATOR_WHITELISTER, ROLE_ID_DAO + ); + + bytes memory encodedMulticall = abi.encodeCall(Multicall.multicall, (calldatas)); + + // console.log("GenerateAccessManagerCallData:"); + // console.logBytes(encodedMulticall); + + return encodedMulticall; + } +} diff --git a/script/DeployProtocolToMainnet.s.sol b/script/DeployProtocolToMainnet.s.sol index cc9d406e..4f1358bf 100644 --- a/script/DeployProtocolToMainnet.s.sol +++ b/script/DeployProtocolToMainnet.s.sol @@ -28,6 +28,7 @@ import { PufferDepositor } from "pufETH/PufferDepositor.sol"; import { PufferProtocolDeployment } from "./DeploymentStructs.sol"; import { SetupAccess } from "script/SetupAccess.s.sol"; import { OperationsCoordinator } from "puffer/OperationsCoordinator.sol"; +import { AVSContractsRegistry } from "puffer/AVSContractsRegistry.sol"; /** * // Check that the simulation @@ -60,6 +61,8 @@ contract DeployProtocolToMainnet is Script { ValidatorTicket validatorTicketImplementation; ERC1967Proxy validatorTicketProxy; + AVSContractsRegistry aVSContractsRegistry; + // Lido address ST_ETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; address LIDO_WITHDRAWAL_QUEUE = 0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1; @@ -153,10 +156,13 @@ contract DeployProtocolToMainnet is Script { restakingOperatorBeacon = new UpgradeableBeacon(address(restakingOperatorImplementation), address(accessManager)); + aVSContractsRegistry = new AVSContractsRegistry(address(accessManager)); + moduleManagerImplementation = new PufferModuleManager({ pufferModuleBeacon: address(pufferModuleBeacon), restakingOperatorBeacon: address(restakingOperatorBeacon), - pufferProtocol: address(pufferProtocolProxy) + pufferProtocol: address(pufferProtocolProxy), + avsContractsRegistry: aVSContractsRegistry }); pufferProtocolImplementation = new PufferProtocol({ @@ -196,6 +202,7 @@ contract DeployProtocolToMainnet is Script { validatorTicket: address(validatorTicketProxy), pufferOracle: address(oracle), operationsCoordinator: address(operationsCoordinator), + aVSContractsRegistry: address(aVSContractsRegistry), pufferDepositor: PUFFER_DEPOSITOR, pufferVault: PUFFER_VAULT, stETH: ST_ETH, diff --git a/script/DeployPuffer.s.sol b/script/DeployPuffer.s.sol index 4a9f9dd4..788d3c34 100644 --- a/script/DeployPuffer.s.sol +++ b/script/DeployPuffer.s.sol @@ -24,6 +24,7 @@ import { ValidatorTicket } from "puffer/ValidatorTicket.sol"; import { OperationsCoordinator } from "puffer/OperationsCoordinator.sol"; import { PufferOracleV2 } from "puffer/PufferOracleV2.sol"; import { IPufferOracleV2 } from "puffer/interface/IPufferOracleV2.sol"; +import { AVSContractsRegistry } from "puffer/AVSContractsRegistry.sol"; /** * @title DeployPuffer @@ -51,6 +52,7 @@ contract DeployPuffer is BaseScript { UpgradeableBeacon restakingOperatorBeacon; PufferModuleManager moduleManager; OperationsCoordinator operationsCoordinator; + AVSContractsRegistry aVSContractsRegistry; address eigenPodManager; address delayedWithdrawalRouter; @@ -132,10 +134,13 @@ contract DeployPuffer is BaseScript { restakingOperatorBeacon = new UpgradeableBeacon(address(restakingOperatorImplementation), address(accessManager)); + aVSContractsRegistry = new AVSContractsRegistry(address(accessManager)); + moduleManager = new PufferModuleManager({ pufferModuleBeacon: address(pufferModuleBeacon), restakingOperatorBeacon: address(restakingOperatorBeacon), - pufferProtocol: address(proxy) + pufferProtocol: address(proxy), + avsContractsRegistry: aVSContractsRegistry }); // Puffer Service implementation @@ -183,6 +188,7 @@ contract DeployPuffer is BaseScript { moduleManager: address(moduleManagerProxy), pufferOracle: address(oracle), operationsCoordinator: address(operationsCoordinator), + aVSContractsRegistry: address(aVSContractsRegistry), timelock: address(0), // overwritten in DeployEverything stETH: address(0), // overwritten in DeployEverything pufferVault: address(0), // overwritten in DeployEverything diff --git a/script/DeployPufferModuleImplementation.s.sol b/script/DeployPufferModuleImplementation.s.sol index 4b8dd1f2..b696befd 100644 --- a/script/DeployPufferModuleImplementation.s.sol +++ b/script/DeployPufferModuleImplementation.s.sol @@ -23,7 +23,6 @@ contract DeployPufferModuleImplementation is Script { address ACCESS_MANAGER = 0xA6c916f85DAfeb6f726E03a1Ce8d08cf835138fF; address PUFFER_MODULE_BEACON = 0x5B81A4579f466fB17af4d8CC0ED51256b94c61D4; - function run() public { require(block.chainid == 17000, "This script is only for Puffer Holesky testnet"); diff --git a/script/DeployPufferModuleManagerImplementation.s.sol b/script/DeployPufferModuleManagerImplementation.s.sol index 30c59b48..e995c263 100644 --- a/script/DeployPufferModuleManagerImplementation.s.sol +++ b/script/DeployPufferModuleManagerImplementation.s.sol @@ -11,6 +11,7 @@ import { BaseScript } from "script/BaseScript.s.sol"; import { stdJson } from "forge-std/StdJson.sol"; import { GuardianModule } from "puffer/GuardianModule.sol"; import { PufferModuleManager } from "puffer/PufferModuleManager.sol"; +import { AVSContractsRegistry } from "puffer/AVSContractsRegistry.sol"; import { UUPSUpgradeable } from "@openzeppelin-contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; /** @@ -20,11 +21,14 @@ contract DeployPufferModuleManagerImplementation is Script { function run() public { require(block.chainid == 17000, "This script is only for Puffer Holesky testnet"); + address accessManager = 0xA6c916f85DAfeb6f726E03a1Ce8d08cf835138fF; + vm.startBroadcast(); PufferModuleManager impl = new PufferModuleManager({ pufferModuleBeacon: 0x5B81A4579f466fB17af4d8CC0ED51256b94c61D4, restakingOperatorBeacon: 0xa7DC88c059F57ADcE41070cEfEFd31F74649a261, - pufferProtocol: 0x705E27D6A6A0c77081D32C07DbDE5A1E139D3F14 + pufferProtocol: 0x705E27D6A6A0c77081D32C07DbDE5A1E139D3F14, + avsContractsRegistry: new AVSContractsRegistry(address(accessManager)) }); UUPSUpgradeable(payable(0xe4695ab93163F91665Ce5b96527408336f070a71)).upgradeToAndCall(address(impl), ""); diff --git a/script/DeploymentStructs.sol b/script/DeploymentStructs.sol index 2a5e5631..345ba3c3 100644 --- a/script/DeploymentStructs.sol +++ b/script/DeploymentStructs.sol @@ -25,6 +25,7 @@ struct PufferProtocolDeployment { address validatorTicket; address pufferOracle; address operationsCoordinator; + address aVSContractsRegistry; address pufferDepositor; // from pufETH repository (dependency) address pufferVault; // from pufETH repository (dependency) address stETH; // from pufETH repository (dependency) diff --git a/script/SetupAccess.s.sol b/script/SetupAccess.s.sol index 87ce9f13..13b171d8 100644 --- a/script/SetupAccess.s.sol +++ b/script/SetupAccess.s.sol @@ -4,6 +4,7 @@ pragma solidity >=0.8.0 <0.9.0; import "forge-std/Script.sol"; import { console } from "forge-std/console.sol"; import { BaseScript } from "script/BaseScript.s.sol"; +import { GenerateAccessManagerCalldata1 } from "script/AccessManagerMigrations/GenerateAccessManagerCalldata1.s.sol"; import { AccessManager } from "openzeppelin/access/manager/AccessManager.sol"; import { Multicall } from "openzeppelin/utils/Multicall.sol"; import { PufferProtocol } from "puffer/PufferProtocol.sol"; @@ -55,6 +56,11 @@ contract SetupAccess is BaseScript { // console.logBytes(cd); (s,) = address(accessManager).call(cd); require(s, "failed setupAccess GenerateAccessManagerCallData"); + + cd = new GenerateAccessManagerCalldata1().run(deployment.aVSContractsRegistry, DAO); + // console.logBytes(cd); + (s,) = address(accessManager).call(cd); + require(s, "failed setupAccess GenerateAccessManagerCalldata1"); } function _generateAccessCalldata( diff --git a/src/AVSContractsRegistry.sol b/src/AVSContractsRegistry.sol new file mode 100644 index 00000000..ae968d9e --- /dev/null +++ b/src/AVSContractsRegistry.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +import { AccessManaged } from "openzeppelin/access/manager/AccessManaged.sol"; + +/** + * @title AVSContractsRegistry + * @author Puffer Finance + * @custom:security-contact security@puffer.fi + */ +contract AVSContractsRegistry is AccessManaged { + event AvsRegistryCoordinatorSet(address indexed avsRegistryCoordinator, bool isAllowed); + + mapping(address avsRegistryCoordinator => bool allowed) internal _avsRegistryCoordinators; + + constructor(address authority) AccessManaged(authority) { } + + /** + * @notice Sets the boolean for the AVS registry coordinator contract + * @param avsRegistryCoordinator is the address of the registry coordinator of the AVS + * @param isAllowed is the boolean value to set the coordinator contract is allowed or not + */ + function setAvsRegistryCoordinator(address avsRegistryCoordinator, bool isAllowed) external restricted { + _avsRegistryCoordinators[avsRegistryCoordinator] = isAllowed; + emit AvsRegistryCoordinatorSet(avsRegistryCoordinator, isAllowed); + } + + /** + * @notice Returns `true` if the `avsRegistryCoordinator` contract is allowed + */ + function isAllowedRegistryCoordinator(address avsRegistryCoordinator) external view returns (bool) { + return _avsRegistryCoordinators[avsRegistryCoordinator] == true; + } +} diff --git a/src/PufferModuleManager.sol b/src/PufferModuleManager.sol index ec9d667e..15fd7bfa 100644 --- a/src/PufferModuleManager.sol +++ b/src/PufferModuleManager.sol @@ -18,6 +18,7 @@ import { ISignatureUtils } from "eigenlayer/interfaces/ISignatureUtils.sol"; import { BeaconChainProofs } from "eigenlayer/libraries/BeaconChainProofs.sol"; import { IERC20 } from "openzeppelin/token/ERC20/IERC20.sol"; import { IRegistryCoordinator, IBLSApkRegistry } from "eigenlayer-middleware/interfaces/IRegistryCoordinator.sol"; +import { AVSContractsRegistry } from "puffer/AVSContractsRegistry.sol"; /** * @title PufferModuleManager @@ -40,6 +41,11 @@ contract PufferModuleManager is IPufferModuleManager, AccessManagedUpgradeable, */ address public immutable override PUFFER_PROTOCOL; + /** + * @dev AVS contracts registry + */ + AVSContractsRegistry public immutable AVS_CONTRACTS_REGISTRY; + modifier onlyPufferProtocol() { if (msg.sender != PUFFER_PROTOCOL) { revert Unauthorized(); @@ -47,10 +53,16 @@ contract PufferModuleManager is IPufferModuleManager, AccessManagedUpgradeable, _; } - constructor(address pufferModuleBeacon, address restakingOperatorBeacon, address pufferProtocol) { + constructor( + address pufferModuleBeacon, + address restakingOperatorBeacon, + address pufferProtocol, + AVSContractsRegistry avsContractsRegistry + ) { PUFFER_MODULE_BEACON = pufferModuleBeacon; RESTAKING_OPERATOR_BEACON = restakingOperatorBeacon; PUFFER_PROTOCOL = pufferProtocol; + AVS_CONTRACTS_REGISTRY = avsContractsRegistry; _disableInitializers(); } @@ -352,6 +364,11 @@ contract PufferModuleManager is IPufferModuleManager, AccessManagedUpgradeable, virtual restricted { + // Custom external calls are only allowed to whitelisted registry coordinators + if (!AVS_CONTRACTS_REGISTRY.isAllowedRegistryCoordinator(target)) { + revert Unauthorized(); + } + (bool success, bytes memory response) = restakingOperator.customCalldataCall(target, customCalldata); if (!success) { revert CustomCallFailed(address(restakingOperator), response); diff --git a/test/fork-tests/PufferModuleManagerHoleskyTestnetTest.integration.t.sol b/test/fork-tests/PufferModuleManagerHoleskyTestnetTest.integration.t.sol index 26f70ea0..950ee5cf 100644 --- a/test/fork-tests/PufferModuleManagerHoleskyTestnetTest.integration.t.sol +++ b/test/fork-tests/PufferModuleManagerHoleskyTestnetTest.integration.t.sol @@ -10,6 +10,7 @@ import { PufferModule } from "puffer/PufferModule.sol"; import { IRestakingOperator } from "puffer/interface/IRestakingOperator.sol"; import { IPufferModuleManager } from "puffer/interface/IPufferModuleManager.sol"; import { RestakingOperator } from "puffer/RestakingOperator.sol"; +import { AVSContractsRegistry } from "puffer/AVSContractsRegistry.sol"; import { PufferModuleManager } from "puffer/PufferModuleManager.sol"; import { DeployEverything } from "script/DeployEverything.s.sol"; import { IDelegationManager } from "eigenlayer/interfaces/IDelegationManager.sol"; @@ -85,7 +86,8 @@ contract PufferModuleManagerHoleskyTestnetTest is Test { PufferModuleManager moduleManagerImplementation = new PufferModuleManager({ pufferModuleBeacon: MODULE_BEACON_HOLESKY, restakingOperatorBeacon: RESTAKING_OPERATOR_BEACON, - pufferProtocol: PUFFER_PROTOCOL_HOLESKY + pufferProtocol: PUFFER_PROTOCOL_HOLESKY, + avsContractsRegistry: new AVSContractsRegistry(ACCESS_MANAGER_HOLESKY) }); // Upgrade PufferModuleManager to a new implementation diff --git a/test/helpers/TestHelper.sol b/test/helpers/TestHelper.sol index 4160a508..b59f29f5 100644 --- a/test/helpers/TestHelper.sol +++ b/test/helpers/TestHelper.sol @@ -7,6 +7,7 @@ import { GuardianModule } from "puffer/GuardianModule.sol"; import { PufferOracleV2 } from "puffer/PufferOracleV2.sol"; import { PufferProtocol } from "puffer/PufferProtocol.sol"; import { PufferModuleManager } from "puffer/PufferModuleManager.sol"; +import { AVSContractsRegistry } from "puffer/AVSContractsRegistry.sol"; import { RaveEvidence } from "puffer/struct/RaveEvidence.sol"; import { IGuardianModule } from "puffer/interface/IGuardianModule.sol"; import { UpgradeableBeacon } from "openzeppelin/proxy/beacon/UpgradeableBeacon.sol"; @@ -87,6 +88,7 @@ contract TestHelper is Test, BaseScript { AccessManager public accessManager; IEnclaveVerifier public verifier; OperationsCoordinator public operationsCoordinator; + AVSContractsRegistry public avsContractsRegistry; address public DAO = makeAddr("DAO"); address public timelock; @@ -164,6 +166,7 @@ contract TestHelper is Test, BaseScript { validatorTicket = ValidatorTicket(pufferDeployment.validatorTicket); pufferOracle = PufferOracleV2(pufferDeployment.pufferOracle); operationsCoordinator = OperationsCoordinator(payable(pufferDeployment.operationsCoordinator)); + avsContractsRegistry = AVSContractsRegistry(payable(pufferDeployment.aVSContractsRegistry)); // pufETH dependencies pufferVault = PufferVaultV2(payable(pufferDeployment.pufferVault)); diff --git a/test/unit/PufferModuleManager.t.sol b/test/unit/PufferModuleManager.t.sol index fe548c54..2bab6d94 100644 --- a/test/unit/PufferModuleManager.t.sol +++ b/test/unit/PufferModuleManager.t.sol @@ -4,6 +4,7 @@ pragma solidity >=0.8.0 <0.9.0; import { TestHelper } from "../helpers/TestHelper.sol"; import { PufferModule } from "puffer/PufferModule.sol"; import { PufferProtocol } from "puffer/PufferProtocol.sol"; +import { AVSContractsRegistry } from "puffer/AVSContractsRegistry.sol"; import { IPufferModuleManager } from "puffer/interface/IPufferModuleManager.sol"; import { IPufferModule } from "puffer/interface/IPufferModule.sol"; import { LibGuardianMessages } from "puffer/LibGuardianMessages.sol"; @@ -372,6 +373,41 @@ contract PufferModuleManagerTest is TestHelper { vm.stopPrank(); } + function test_customExternalCall() public { + vm.startPrank(DAO); + IRestakingOperator operator = _createRestakingOperator(); + + bytes memory customCalldata = abi.encodeCall(PufferModuleManagerTest.getMagicNumber, ()); + + // Not whitelisted, revert + vm.expectRevert(Unauthorized.selector); + pufferModuleManager.customExternalCall(operator, address(this), customCalldata); + + // Generate whitelist cd + bytes memory whitelistCalldata = + abi.encodeWithSelector(AVSContractsRegistry.setAvsRegistryCoordinator.selector, address(this), true); + + // Schedule adding the registry coordinator contract (this contract as a mock) to the whitelist + accessManager.schedule(address(avsContractsRegistry), whitelistCalldata, 0); + + // Advance the timestamp + vm.warp(block.timestamp + 1 days + 1); + // execute the whitelist calldata + accessManager.execute(address(avsContractsRegistry), whitelistCalldata); + + // Now it works + vm.expectEmit(true, true, true, true); + emit IPufferModuleManager.CustomCallSucceeded({ + restakingOperator: address(operator), + target: address(this), + customCalldata: customCalldata, + response: abi.encode(85858585) + }); + pufferModuleManager.customExternalCall( + operator, address(this), abi.encodeCall(PufferModuleManagerTest.getMagicNumber, ()) + ); + } + function _createPufferModule(bytes32 moduleName) internal returns (address module) { vm.assume(pufferProtocol.getModuleAddress(moduleName) == address(0)); vm.startPrank(DAO); @@ -409,6 +445,10 @@ contract PufferModuleManagerTest is TestHelper { assertEq(details.stakerOptOutWindowBlocks, 0, "blocks"); return operator; } + + function getMagicNumber() external view returns (uint256) { + return 85858585; + } } struct MerkleProofData { From dc96b3d3a94518fc7efbd818bc74317816a81adf Mon Sep 17 00:00:00 2001 From: Benjamin Date: Thu, 9 May 2024 18:27:23 +0200 Subject: [PATCH 04/10] change wording --- .../GenerateAccessManagerCalldata1.s.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/script/AccessManagerMigrations/GenerateAccessManagerCalldata1.s.sol b/script/AccessManagerMigrations/GenerateAccessManagerCalldata1.s.sol index 6b8d3430..4a18433b 100644 --- a/script/AccessManagerMigrations/GenerateAccessManagerCalldata1.s.sol +++ b/script/AccessManagerMigrations/GenerateAccessManagerCalldata1.s.sol @@ -6,7 +6,7 @@ import { AccessManager } from "openzeppelin/access/manager/AccessManager.sol"; import { Multicall } from "openzeppelin/utils/Multicall.sol"; import { console } from "forge-std/console.sol"; import { AVSContractsRegistry } from "../../src/AVSContractsRegistry.sol"; -import { ROLE_ID_AVS_COORDINATOR_WHITELISTER, ROLE_ID_DAO } from "pufETHScript/Roles.sol"; +import { ROLE_ID_AVS_COORDINATOR_ALLOWLISTER, ROLE_ID_DAO } from "pufETHScript/Roles.sol"; /** * @title GenerateAccessManagerCalldata1 @@ -28,20 +28,20 @@ contract GenerateAccessManagerCalldata1 is Script { AccessManager.setTargetFunctionRole.selector, avsContractsRegistry, whitelisterSelectors, - ROLE_ID_AVS_COORDINATOR_WHITELISTER + ROLE_ID_AVS_COORDINATOR_ALLOWLISTER ); // Whitelister has 1 day timelock to add new coordinators calldatas[1] = abi.encodeWithSelector( AccessManager.grantRole.selector, - ROLE_ID_AVS_COORDINATOR_WHITELISTER, + ROLE_ID_AVS_COORDINATOR_ALLOWLISTER, whitelister, 1 days // 1 day timelock ); // The role guardian can cancel calldatas[2] = abi.encodeWithSelector( - AccessManager.setRoleGuardian.selector, ROLE_ID_AVS_COORDINATOR_WHITELISTER, ROLE_ID_DAO + AccessManager.setRoleGuardian.selector, ROLE_ID_AVS_COORDINATOR_ALLOWLISTER, ROLE_ID_DAO ); bytes memory encodedMulticall = abi.encodeCall(Multicall.multicall, (calldatas)); From 635820e796dffe9e4e33c85ce6b9fa9cec1c73b2 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Thu, 9 May 2024 18:27:47 +0200 Subject: [PATCH 05/10] submodules --- lib/pufETH | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pufETH b/lib/pufETH index 0ac03edb..cad7941a 160000 --- a/lib/pufETH +++ b/lib/pufETH @@ -1 +1 @@ -Subproject commit 0ac03edb16d61df1a61dca41722d5d1a4c634c06 +Subproject commit cad7941a1786c9050c8e7088e08b1a5ee79d742c From e3415e77cb353cc6f80e0db6f76f9219e76ae274 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Tue, 14 May 2024 13:21:50 -0700 Subject: [PATCH 06/10] adds selector allowlisting --- src/AVSContractsRegistry.sol | 28 ++++++++++++++------ src/PufferModuleManager.sol | 2 +- test/unit/PufferModuleManager.t.sol | 40 +++++++++++++++++++++++------ 3 files changed, 53 insertions(+), 17 deletions(-) diff --git a/src/AVSContractsRegistry.sol b/src/AVSContractsRegistry.sol index ae968d9e..563bd1bb 100644 --- a/src/AVSContractsRegistry.sol +++ b/src/AVSContractsRegistry.sol @@ -9,26 +9,38 @@ import { AccessManaged } from "openzeppelin/access/manager/AccessManaged.sol"; * @custom:security-contact security@puffer.fi */ contract AVSContractsRegistry is AccessManaged { - event AvsRegistryCoordinatorSet(address indexed avsRegistryCoordinator, bool isAllowed); + event AvsRegistryCoordinatorSet(address indexed avsRegistryCoordinator, bytes4 selector, bool isAllowed); - mapping(address avsRegistryCoordinator => bool allowed) internal _avsRegistryCoordinators; + mapping(address avsRegistryCoordinator => mapping(bytes4 selector => bool allowed)) internal + _avsRegistryCoordinators; constructor(address authority) AccessManaged(authority) { } /** * @notice Sets the boolean for the AVS registry coordinator contract * @param avsRegistryCoordinator is the address of the registry coordinator of the AVS - * @param isAllowed is the boolean value to set the coordinator contract is allowed or not + * @param selector is the signature of the function + * @param isAllowed is the boolean value to set if coordinator contract and signature are allowed or not */ - function setAvsRegistryCoordinator(address avsRegistryCoordinator, bool isAllowed) external restricted { - _avsRegistryCoordinators[avsRegistryCoordinator] = isAllowed; - emit AvsRegistryCoordinatorSet(avsRegistryCoordinator, isAllowed); + function setAvsRegistryCoordinator(address avsRegistryCoordinator, bytes4 selector, bool isAllowed) + external + restricted + { + _avsRegistryCoordinators[avsRegistryCoordinator][selector] = isAllowed; + emit AvsRegistryCoordinatorSet(avsRegistryCoordinator, selector, isAllowed); } /** * @notice Returns `true` if the `avsRegistryCoordinator` contract is allowed */ - function isAllowedRegistryCoordinator(address avsRegistryCoordinator) external view returns (bool) { - return _avsRegistryCoordinators[avsRegistryCoordinator] == true; + function isAllowedRegistryCoordinator(address avsRegistryCoordinator, bytes calldata customCalldata) + external + view + returns (bool) + { + // Extract the function selector (first 4 bytes of customCalldata) + bytes4 selector = bytes4(customCalldata[:4]); + + return _avsRegistryCoordinators[avsRegistryCoordinator][selector] == true; } } diff --git a/src/PufferModuleManager.sol b/src/PufferModuleManager.sol index 15fd7bfa..055172e2 100644 --- a/src/PufferModuleManager.sol +++ b/src/PufferModuleManager.sol @@ -365,7 +365,7 @@ contract PufferModuleManager is IPufferModuleManager, AccessManagedUpgradeable, restricted { // Custom external calls are only allowed to whitelisted registry coordinators - if (!AVS_CONTRACTS_REGISTRY.isAllowedRegistryCoordinator(target)) { + if (!AVS_CONTRACTS_REGISTRY.isAllowedRegistryCoordinator(target, customCalldata)) { revert Unauthorized(); } diff --git a/test/unit/PufferModuleManager.t.sol b/test/unit/PufferModuleManager.t.sol index 52c7b633..4892c10e 100644 --- a/test/unit/PufferModuleManager.t.sol +++ b/test/unit/PufferModuleManager.t.sol @@ -379,21 +379,25 @@ contract PufferModuleManagerTest is TestHelper { bytes memory customCalldata = abi.encodeCall(PufferModuleManagerTest.getMagicNumber, ()); - // Not whitelisted, revert + // Not allowlisted, revert vm.expectRevert(Unauthorized.selector); pufferModuleManager.customExternalCall(operator, address(this), customCalldata); - // Generate whitelist cd - bytes memory whitelistCalldata = - abi.encodeWithSelector(AVSContractsRegistry.setAvsRegistryCoordinator.selector, address(this), true); + // Generate allowlist cd + bytes memory allowlistCalldata = abi.encodeWithSelector( + AVSContractsRegistry.setAvsRegistryCoordinator.selector, + address(this), + PufferModuleManagerTest.getMagicNumber.selector, + true + ); - // Schedule adding the registry coordinator contract (this contract as a mock) to the whitelist - accessManager.schedule(address(avsContractsRegistry), whitelistCalldata, 0); + // Schedule adding the registry coordinator contract (this contract as a mock) to the allowlist + accessManager.schedule(address(avsContractsRegistry), allowlistCalldata, 0); // Advance the timestamp vm.warp(block.timestamp + 1 days + 1); - // execute the whitelist calldata - accessManager.execute(address(avsContractsRegistry), whitelistCalldata); + // execute the allowlist calldata + accessManager.execute(address(avsContractsRegistry), allowlistCalldata); // Now it works vm.expectEmit(true, true, true, true); @@ -406,6 +410,26 @@ contract PufferModuleManagerTest is TestHelper { pufferModuleManager.customExternalCall( operator, address(this), abi.encodeCall(PufferModuleManagerTest.getMagicNumber, ()) ); + + // Generate allowlist cd to remove the selector from the allowlist + allowlistCalldata = abi.encodeWithSelector( + AVSContractsRegistry.setAvsRegistryCoordinator.selector, + address(this), + PufferModuleManagerTest.getMagicNumber.selector, + false + ); + + // Schedule adding the registry coordinator contract (this contract as a mock) to the allowlist + accessManager.schedule(address(avsContractsRegistry), allowlistCalldata, 0); + + // Advance the timestamp + vm.warp(block.timestamp + 1 days + 1); + // execute the allowlist calldata + accessManager.execute(address(avsContractsRegistry), allowlistCalldata); + + // Not allowlisted, revert + vm.expectRevert(Unauthorized.selector); + pufferModuleManager.customExternalCall(operator, address(this), customCalldata); } function _createPufferModule(bytes32 moduleName) internal returns (address module) { From 32e156dba39b636510a28fee628779c3887d3506 Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Tue, 14 May 2024 22:11:26 -0700 Subject: [PATCH 07/10] mainnet upgrade script --- .../UpgradeRestakingOperator.s.sol | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 script/MainnetContractMigrations/UpgradeRestakingOperator.s.sol diff --git a/script/MainnetContractMigrations/UpgradeRestakingOperator.s.sol b/script/MainnetContractMigrations/UpgradeRestakingOperator.s.sol new file mode 100644 index 00000000..87f31f2b --- /dev/null +++ b/script/MainnetContractMigrations/UpgradeRestakingOperator.s.sol @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity >=0.8.0 <0.9.0; + +import "forge-std/Script.sol"; +import { BaseScript } from "script/BaseScript.s.sol"; +import { PufferProtocol } from "puffer/PufferProtocol.sol"; +import { AccessManager } from "openzeppelin/access/manager/AccessManager.sol"; +import { BaseScript } from "script/BaseScript.s.sol"; +import { stdJson } from "forge-std/StdJson.sol"; +import { PufferModuleManager } from "puffer/PufferModuleManager.sol"; +import { AVSContractsRegistry } from "puffer/AVSContractsRegistry.sol"; +import { UUPSUpgradeable } from "@openzeppelin-contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import { UpgradeableBeacon } from "openzeppelin/proxy/beacon/UpgradeableBeacon.sol"; +import { RestakingOperator } from "puffer/RestakingOperator.sol"; +import { IDelegationManager } from "eigenlayer/interfaces/IDelegationManager.sol"; +import { ISlasher } from "eigenlayer/interfaces/ISlasher.sol"; +import { GenerateAccessManagerCalldata1 } from "script/AccessManagerMigrations/GenerateAccessManagerCalldata1.s.sol"; + +/** + * forge script script/UpgradeRestakingOperator.s.sol:UpgradeRestakingOperator --rpc-url=$RPC_URL --private-key $PK + */ +contract UpgradeRestakingOperator is Script { + address DELEGATION_MANAGER = 0x39053D51B77DC0d36036Fc1fCc8Cb819df8Ef37A; + address EIGEN_SLASHER = 0xD92145c07f8Ed1D392c1B88017934E301CC1c3Cd; + address MODULE_MANAGER_PROXY = 0x9E1E4fCb49931df5743e659ad910d331735C3860; + address MODULE_BEACON = 0xdd38A5a7789C74fc7F64556fc772343658EEBb04; + address RESTAKING_OPERATOR_BEACON = 0x6756B856Dd3843C84249a6A31850cB56dB824c4B; + address PUFFER_PROTOCOL = 0xf7b6B32492c2e13799D921E84202450131bd238B; + address DAO = 0xC0896ab1A8cae8c2C1d27d011eb955Cca955580d; + address ACCESS_MANAGER = 0x8c1686069474410E6243425f4a10177a94EBEE11; + + function run() public { + require(block.chainid == 1, "This script is only for Puffer Mainnet"); + vm.startBroadcast(); + + AVSContractsRegistry avsRegistry = new AVSContractsRegistry(address(ACCESS_MANAGER)); + + PufferModuleManager pufferModuleManagerImpl = new PufferModuleManager({ + pufferModuleBeacon: MODULE_BEACON, + restakingOperatorBeacon: RESTAKING_OPERATOR_BEACON, + pufferProtocol: PUFFER_PROTOCOL, + avsContractsRegistry: avsRegistry + }); + + RestakingOperator restakingOperatorImpl = new RestakingOperator({ + delegationManager: IDelegationManager(DELEGATION_MANAGER), + slasher: ISlasher(EIGEN_SLASHER), + moduleManager: PufferModuleManager(MODULE_MANAGER_PROXY) + }); + + bytes memory accessCd = new GenerateAccessManagerCalldata1().run(address(avsRegistry), DAO); + + bytes memory cd1 = abi.encodeCall(UUPSUpgradeable.upgradeToAndCall, (address(pufferModuleManagerImpl), "")); + bytes memory cd2 = abi.encodeCall(UpgradeableBeacon.upgradeTo, address(restakingOperatorImpl)); + bytes memory cd3 = abi.encodeCall(AccessManager.execute, (MODULE_MANAGER_PROXY, cd1)); + bytes memory cd4 = abi.encodeCall(AccessManager.execute, (RESTAKING_OPERATOR_BEACON, cd2)); + + // calldata to execute using the timelock contract. setting the target as the Access Manager + console.logBytes(cd3); + console.logBytes(cd4); + console.logBytes(accessCd); + + // AccessManager is the owner of upgradeable beacon for restaking operator & module manager + // AccessManager(ACCESS_MANAGER).execute(MODULE_MANAGER_PROXY, cd1); + // AccessManager(ACCESS_MANAGER).execute(RESTAKING_OPERATOR_BEACON, cd2); + } +} From 3584b53148c1761526b85c36d7afa4a3d8c6448d Mon Sep 17 00:00:00 2001 From: 0xwalid Date: Tue, 14 May 2024 22:36:23 -0700 Subject: [PATCH 08/10] adds access setup for module manager --- .../GenerateAccessManagerCalldata1.s.sol | 17 +++++++++++++++-- .../UpgradeRestakingOperator.s.sol | 3 ++- script/SetupAccess.s.sol | 2 +- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/script/AccessManagerMigrations/GenerateAccessManagerCalldata1.s.sol b/script/AccessManagerMigrations/GenerateAccessManagerCalldata1.s.sol index 4a18433b..6e38fda4 100644 --- a/script/AccessManagerMigrations/GenerateAccessManagerCalldata1.s.sol +++ b/script/AccessManagerMigrations/GenerateAccessManagerCalldata1.s.sol @@ -7,6 +7,7 @@ import { Multicall } from "openzeppelin/utils/Multicall.sol"; import { console } from "forge-std/console.sol"; import { AVSContractsRegistry } from "../../src/AVSContractsRegistry.sol"; import { ROLE_ID_AVS_COORDINATOR_ALLOWLISTER, ROLE_ID_DAO } from "pufETHScript/Roles.sol"; +import { PufferModuleManager } from "puffer/PufferModuleManager.sol"; /** * @title GenerateAccessManagerCalldata1 @@ -18,8 +19,12 @@ import { ROLE_ID_AVS_COORDINATOR_ALLOWLISTER, ROLE_ID_DAO } from "pufETHScript/R * 3. timelock.executeTransaction(address(accessManager), encodedMulticall, 1) */ contract GenerateAccessManagerCalldata1 is Script { - function run(address avsContractsRegistry, address whitelister) public pure returns (bytes memory) { - bytes[] memory calldatas = new bytes[](3); + function run(address moduleManager, address avsContractsRegistry, address whitelister) + public + pure + returns (bytes memory) + { + bytes[] memory calldatas = new bytes[](4); bytes4[] memory whitelisterSelectors = new bytes4[](1); whitelisterSelectors[0] = AVSContractsRegistry.setAvsRegistryCoordinator.selector; @@ -44,6 +49,14 @@ contract GenerateAccessManagerCalldata1 is Script { AccessManager.setRoleGuardian.selector, ROLE_ID_AVS_COORDINATOR_ALLOWLISTER, ROLE_ID_DAO ); + bytes4[] memory pufferModuleManagerSelectors = new bytes4[](1); + + pufferModuleManagerSelectors[0] = PufferModuleManager.customExternalCall.selector; + + calldatas[3] = abi.encodeWithSelector( + AccessManager.setTargetFunctionRole.selector, moduleManager, pufferModuleManagerSelectors, ROLE_ID_DAO + ); + bytes memory encodedMulticall = abi.encodeCall(Multicall.multicall, (calldatas)); // console.log("GenerateAccessManagerCallData:"); diff --git a/script/MainnetContractMigrations/UpgradeRestakingOperator.s.sol b/script/MainnetContractMigrations/UpgradeRestakingOperator.s.sol index 87f31f2b..7a8b81e5 100644 --- a/script/MainnetContractMigrations/UpgradeRestakingOperator.s.sol +++ b/script/MainnetContractMigrations/UpgradeRestakingOperator.s.sol @@ -48,7 +48,8 @@ contract UpgradeRestakingOperator is Script { moduleManager: PufferModuleManager(MODULE_MANAGER_PROXY) }); - bytes memory accessCd = new GenerateAccessManagerCalldata1().run(address(avsRegistry), DAO); + bytes memory accessCd = + new GenerateAccessManagerCalldata1().run(MODULE_MANAGER_PROXY, address(avsRegistry), DAO); bytes memory cd1 = abi.encodeCall(UUPSUpgradeable.upgradeToAndCall, (address(pufferModuleManagerImpl), "")); bytes memory cd2 = abi.encodeCall(UpgradeableBeacon.upgradeTo, address(restakingOperatorImpl)); diff --git a/script/SetupAccess.s.sol b/script/SetupAccess.s.sol index fb04e222..e82f7af7 100644 --- a/script/SetupAccess.s.sol +++ b/script/SetupAccess.s.sol @@ -57,7 +57,7 @@ contract SetupAccess is BaseScript { (s,) = address(accessManager).call(cd); require(s, "failed setupAccess GenerateAccessManagerCallData"); - cd = new GenerateAccessManagerCalldata1().run(deployment.aVSContractsRegistry, DAO); + cd = new GenerateAccessManagerCalldata1().run(deployment.moduleManager, deployment.aVSContractsRegistry, DAO); // console.logBytes(cd); (s,) = address(accessManager).call(cd); require(s, "failed setupAccess GenerateAccessManagerCalldata1"); From 5c83a3a06d158cd5779eaf3a83d5f6b48bad530c Mon Sep 17 00:00:00 2001 From: Benjamin Date: Wed, 15 May 2024 09:39:16 +0200 Subject: [PATCH 09/10] silence slither error --- src/AVSContractsRegistry.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AVSContractsRegistry.sol b/src/AVSContractsRegistry.sol index 563bd1bb..bb36a4bd 100644 --- a/src/AVSContractsRegistry.sol +++ b/src/AVSContractsRegistry.sol @@ -41,6 +41,6 @@ contract AVSContractsRegistry is AccessManaged { // Extract the function selector (first 4 bytes of customCalldata) bytes4 selector = bytes4(customCalldata[:4]); - return _avsRegistryCoordinators[avsRegistryCoordinator][selector] == true; + return _avsRegistryCoordinators[avsRegistryCoordinator][selector]; } } From 92c2672f404e4c9e49d944d045d3d47e5d617fb2 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Wed, 15 May 2024 09:56:06 +0200 Subject: [PATCH 10/10] update pufETH submodule --- lib/pufETH | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pufETH b/lib/pufETH index cad7941a..e802aaad 160000 --- a/lib/pufETH +++ b/lib/pufETH @@ -1 +1 @@ -Subproject commit cad7941a1786c9050c8e7088e08b1a5ee79d742c +Subproject commit e802aaad9538b5914317839b54d5363fa1748cac