From e985922637dde3d9b70b146c3f5c5d6cb8ac299d Mon Sep 17 00:00:00 2001 From: MinisculeTarantula Date: Fri, 15 Nov 2024 15:32:56 -0800 Subject: [PATCH] feat: regen align-environments branch an attempt to recreate and fix https://github.com/Layr-Labs/eigenlayer-contracts/pull/794 modifications had to be made due to changes to configs etc due to work on Zeus this also notably excludes deploy scripts for the EIGEN token and TimelockControllers specifically for Holesky, where they were already used --- script/NoDelayTimelock.sol | 115 ++++ script/configs/mainnet.json | 14 +- script/utils/CurrentConfigCheck.s.sol | 568 ++++++++++++++++++ script/utils/ExistingDeploymentParser.sol | 270 +++++---- script/utils/TimelockEncoding.sol | 108 ++++ script/utils/TxEncodingInterfaces.sol | 27 + .../interfaces/IDelegationFaucet.sol | 41 -- 7 files changed, 971 insertions(+), 172 deletions(-) create mode 100644 script/NoDelayTimelock.sol create mode 100644 script/utils/CurrentConfigCheck.s.sol create mode 100644 script/utils/TimelockEncoding.sol create mode 100644 script/utils/TxEncodingInterfaces.sol delete mode 100644 src/contracts/interfaces/IDelegationFaucet.sol diff --git a/script/NoDelayTimelock.sol b/script/NoDelayTimelock.sol new file mode 100644 index 000000000..c3ca33983 --- /dev/null +++ b/script/NoDelayTimelock.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: BSD-3-Clause +pragma solidity ^0.8.12; + +/// @notice Modified version of our mainnet timelock for use on Holesky +/// Specifically, this removes SafeMath and changes `MINIMUM_DELAY` to `0 days` +/// +/// See original version here: https://github.com/compound-finance/compound-protocol/blob/a3214f67b73310d547e00fc578e8355911c9d376/contracts/Timelock.sol +contract NoDelayTimelock { + + event NewAdmin(address indexed newAdmin); + event NewPendingAdmin(address indexed newPendingAdmin); + event NewDelay(uint indexed newDelay); + event CancelTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta); + event ExecuteTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta); + event QueueTransaction(bytes32 indexed txHash, address indexed target, uint value, string signature, bytes data, uint eta); + + uint public constant GRACE_PERIOD = 14 days; + uint public constant MINIMUM_DELAY = 0; + uint public constant MAXIMUM_DELAY = 30 days; + + address public admin; + address public pendingAdmin; + uint public delay; + + mapping (bytes32 => bool) public queuedTransactions; + + + constructor(address admin_, uint delay_) { + require(delay_ >= MINIMUM_DELAY, "Timelock::constructor: Delay must exceed minimum delay."); + require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay."); + + admin = admin_; + delay = delay_; + } + + fallback() external payable { } + + receive() external payable { } + + function setDelay(uint delay_) public { + require(msg.sender == address(this), "Timelock::setDelay: Call must come from Timelock."); + require(delay_ >= MINIMUM_DELAY, "Timelock::setDelay: Delay must exceed minimum delay."); + require(delay_ <= MAXIMUM_DELAY, "Timelock::setDelay: Delay must not exceed maximum delay."); + delay = delay_; + + emit NewDelay(delay); + } + + function acceptAdmin() public { + require(msg.sender == pendingAdmin, "Timelock::acceptAdmin: Call must come from pendingAdmin."); + admin = msg.sender; + pendingAdmin = address(0); + + emit NewAdmin(admin); + } + + function setPendingAdmin(address pendingAdmin_) public { + require(msg.sender == address(this), "Timelock::setPendingAdmin: Call must come from Timelock."); + pendingAdmin = pendingAdmin_; + + emit NewPendingAdmin(pendingAdmin); + } + + function queueTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public returns (bytes32) { + require(msg.sender == admin, "Timelock::queueTransaction: Call must come from admin."); + require(eta >= getBlockTimestamp() + delay, "Timelock::queueTransaction: Estimated execution block must satisfy delay."); + + bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); + queuedTransactions[txHash] = true; + + emit QueueTransaction(txHash, target, value, signature, data, eta); + return txHash; + } + + function cancelTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public { + require(msg.sender == admin, "Timelock::cancelTransaction: Call must come from admin."); + + bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); + queuedTransactions[txHash] = false; + + emit CancelTransaction(txHash, target, value, signature, data, eta); + } + + function executeTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) public payable returns (bytes memory) { + require(msg.sender == admin, "Timelock::executeTransaction: Call must come from admin."); + + bytes32 txHash = keccak256(abi.encode(target, value, signature, data, eta)); + require(queuedTransactions[txHash], "Timelock::executeTransaction: Transaction hasn't been queued."); + require(getBlockTimestamp() >= eta, "Timelock::executeTransaction: Transaction hasn't surpassed time lock."); + require(getBlockTimestamp() <= eta + GRACE_PERIOD, "Timelock::executeTransaction: Transaction is stale."); + + queuedTransactions[txHash] = false; + + bytes memory callData; + + if (bytes(signature).length == 0) { + callData = data; + } else { + callData = abi.encodePacked(bytes4(keccak256(bytes(signature))), data); + } + + // solium-disable-next-line security/no-call-value + (bool success, bytes memory returnData) = target.call{value: value}(callData); + require(success, "Timelock::executeTransaction: Transaction execution reverted."); + + emit ExecuteTransaction(txHash, target, value, signature, data, eta); + + return returnData; + } + + function getBlockTimestamp() internal view returns (uint) { + // solium-disable-next-line security/no-block-members + return block.timestamp; + } +} diff --git a/script/configs/mainnet.json b/script/configs/mainnet.json index 3b42bc362..fac461407 100644 --- a/script/configs/mainnet.json +++ b/script/configs/mainnet.json @@ -26,7 +26,8 @@ "pauserMultisig": "0x5050389572f2d220ad927CcbeA0D406831012390", "pauserRegistry": "0x0c431C66F4dE941d089625E5B423D00707977060", "proxyAdmin": "0x8b9566AdA63B64d1E1dcF1418b43fd1433b72444", - "timelock": "0xA6Db1A8C5a981d1536266D2a393c5F8dDb210EAF" + "timelock": "0xA6Db1A8C5a981d1536266D2a393c5F8dDb210EAF", + "foundationMultisig": "0xbb00DDa2832850a43840A3A86515E3Fe226865F2" }, "core": { "avsDirectory": { @@ -41,7 +42,7 @@ }, "rewardsCoordinator": { "proxy": "0x7750d328b314EfFa365A0402CcfD489B80B0adda", - "impl": "0x5bf7c13D5FAdba224ECB3D5C0a67A231D1628785", + "impl": "0xb6738A8E7793D44c5895B6A6F2a62F6bF86Ba8d2", "pendingImpl": "0x0000000000000000000000000000000000000000" }, "slasher": { @@ -106,19 +107,22 @@ "proxy": "0x83E9115d334D248Ce39a6f36144aEaB5b3456e75", "impl": "0xB91c69Af3eE022bd0a59Da082945914BFDcEFFE3", "pendingImpl": "0x0000000000000000000000000000000000000000", - "proxyAdmin": "0x3f5Ab2D4418d38568705bFd6672630fCC3435CC9" + "proxyAdmin": "0x3f5Ab2D4418d38568705bFd6672630fCC3435CC9", + "timelockController": "0xd6EC41E453C5E7dA5494f4d51A053Ab571712E6f" }, "EIGEN": { "proxy": "0xec53bf9167f50cdeb3ae105f56099aaab9061f83", "impl": "0x7ec354c84680112d3cff1544ec1eb19ca583700b", "pendingImpl": "0x0000000000000000000000000000000000000000", - "proxyAdmin": "0xB8915E195121f2B5D989Ec5727fd47a5259F1CEC" + "proxyAdmin": "0xB8915E195121f2B5D989Ec5727fd47a5259F1CEC", + "timelockController": "0x2520C6b2C1FBE1813AB5c7c1018CDa39529e9FF2" }, "eigenStrategy": { "proxy": "0xaCB55C530Acdb2849e6d4f36992Cd8c9D50ED8F7", "impl": "0x27e7a3a81741b9fcc5ad7edcbf9f8a72a5c00428", "pendingImpl": "0x0000000000000000000000000000000000000000" } - } + }, + "emptyContract": "0x1f96861fEFa1065a5A96F20Deb6D8DC3ff48F7f9" } } \ No newline at end of file diff --git a/script/utils/CurrentConfigCheck.s.sol b/script/utils/CurrentConfigCheck.s.sol new file mode 100644 index 000000000..7c2e917b1 --- /dev/null +++ b/script/utils/CurrentConfigCheck.s.sol @@ -0,0 +1,568 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.12; + +import "./ExistingDeploymentParser.sol"; +import "./TimelockEncoding.sol"; +import "../NoDelayTimelock.sol"; + +/** + * forge script script/utils/CurrentConfigCheck.s.sol:CurrentConfigCheck -vvv --sig "run(string)" $NETWORK_NAME + * NETWORK_NAME options are currently preprod-holesky, testnet-holesky, mainnet, local + */ +contract CurrentConfigCheck is ExistingDeploymentParser, TimelockEncoding { + string deployedContractsConfig; + string intialDeploymentParams; + string forkUrl; + string emptyString; + + address public protocolCouncilMultisig; + TimelockController public protocolTimelockController; + TimelockController public protocolTimelockController_BEIGEN; + + address public beigenExecutorMultisig; + + function run(string memory networkName) public virtual { + startChainFork(networkName); + + _parseDeployedContracts(deployedContractsConfig); + _parseInitialDeploymentParams(intialDeploymentParams); + + // Sanity Checks + _verifyContractPointers(); + _verifyImplementations(); + _verifyContractsInitialized(false); + // _verifyInitializationParams(); + + // bytes memory data = abi.encodeWithSelector(ProxyAdmin.changeProxyAdmin.selector, address(bEIGEN), address(beigenTokenProxyAdmin)); + // bytes memory data = abi.encodeWithSignature("swapOwner(address,address,address)", + // 0xCb8d2f9e55Bc7B1FA9d089f9aC80C583D2BDD5F7, + // 0xcF19CE0561052a7A7Ff21156730285997B350A7D, + // 0xFddd03C169E3FD9Ea4a9548dDC4BedC6502FE239 + // ); + // bytes memory callToExecutor = encodeForExecutor({ + // from: communityMultisig, + // to: address(executorMultisig), + // value: 0, + // data: data, + // operation: ISafe.Operation.Call + // }); + + // vm.prank(communityMultisig); + // (bool success, /*bytes memory returndata*/) = executorMultisig.call(callToExecutor); + // require(success, "call to executorMultisig failed"); + + // vm.startPrank(foundationMultisig); + // Ownable(address(bEIGEN)).transferOwnership(address(eigenTokenTimelockController)); + // Ownable(address(EIGEN)).transferOwnership(address(eigenTokenTimelockController)); + // vm.stopPrank(); + + checkGovernanceConfiguration_Current(); + + // simulateProtocolCouncilUpgrade(networkName); + } + + function startChainFork(string memory networkName) public virtual { + if (keccak256(abi.encodePacked(networkName)) == keccak256(abi.encodePacked("preprod-holesky"))) { + deployedContractsConfig = "script/configs/preprod.json"; + intialDeploymentParams = "script/configs/preprod.json"; + forkUrl = vm.envString("RPC_HOLESKY"); + uint256 forkId = vm.createFork(forkUrl); + vm.selectFork(forkId); + } else if (keccak256(abi.encodePacked(networkName)) == keccak256(abi.encodePacked("testnet-holesky"))) { + deployedContractsConfig = "script/configs/holesky.json"; + intialDeploymentParams = "script/configs/holesky.json"; + forkUrl = vm.envString("RPC_HOLESKY"); + uint256 forkId = vm.createFork(forkUrl); + vm.selectFork(forkId); + } else if (keccak256(abi.encodePacked(networkName)) == keccak256(abi.encodePacked("mainnet"))) { + deployedContractsConfig = "script/configs/mainnet.json"; + intialDeploymentParams = "script/configs/mainnet.json"; + forkUrl = vm.envString("RPC_MAINNET"); + uint256 forkId = vm.createFork(forkUrl); + vm.selectFork(forkId); + } else if (keccak256(abi.encodePacked(networkName)) == keccak256(abi.encodePacked("local"))) { + deployedContractsConfig = "script/configs/local/deploy_from_scratch.anvil.config.json"; + intialDeploymentParams = "script/configs/local/deploy_from_scratch.anvil.config.json"; + } + + require(keccak256(abi.encodePacked(deployedContractsConfig)) != keccak256(abi.encodePacked(emptyString)), + "deployedContractsConfig cannot be unset"); + require(keccak256(abi.encodePacked(intialDeploymentParams)) != keccak256(abi.encodePacked(emptyString)), + "intialDeploymentParams cannot be unset"); + + // read and log the chainID + uint256 currentChainId = block.chainid; + emit log_named_uint("You are parsing on ChainID", currentChainId); + require(currentChainId == 1 || currentChainId == 17000 || currentChainId == 31337, + "script is only for mainnet or holesky or local environment"); + } + + // check governance configuration + function checkGovernanceConfiguration_Current() public { + assertEq(eigenLayerProxyAdmin.owner(), executorMultisig, + "eigenLayerProxyAdmin.owner() != executorMultisig"); + assertEq(delegationManager.owner(), executorMultisig, + "delegationManager.owner() != executorMultisig"); + assertEq(strategyManager.owner(), executorMultisig, + "strategyManager.owner() != executorMultisig"); + assertEq(strategyManager.strategyWhitelister(), address(strategyFactory), + "strategyManager.strategyWhitelister() != address(strategyFactory)"); + assertEq(strategyFactory.owner(), operationsMultisig, + "strategyFactory.owner() != operationsMultisig"); + assertEq(avsDirectory.owner(), executorMultisig, + "avsDirectory.owner() != executorMultisig"); + assertEq(rewardsCoordinator.owner(), operationsMultisig, + "rewardsCoordinator.owner() != operationsMultisig"); + assertEq(eigenLayerPauserReg.unpauser(), executorMultisig, + "eigenLayerPauserReg.unpauser() != operationsMultisig"); + require(eigenLayerPauserReg.isPauser(operationsMultisig), + "operationsMultisig does not have pausing permissions"); + require(eigenLayerPauserReg.isPauser(executorMultisig), + "executorMultisig does not have pausing permissions"); + require(eigenLayerPauserReg.isPauser(pauserMultisig), + "pauserMultisig does not have pausing permissions"); + + address timelockAdmin = NoDelayTimelock(payable(timelock)).admin(); + assertEq(timelockAdmin, operationsMultisig, + "timelockAdmin != operationsMultisig"); + + (bool success, bytes memory returndata) = executorMultisig.staticcall(abi.encodeWithSignature("getOwners()")); + require(success, "call to executorMultisig.getOwners() failed"); + address[] memory executorMultisigOwners = abi.decode(returndata, (address[])); + require(executorMultisigOwners.length == 2, + "executorMultisig owners wrong length"); + bool timelockInOwners; + bool communityMultisigInOwners; + for (uint256 i = 0; i < 2; ++i) { + if (executorMultisigOwners[i] == timelock) { + timelockInOwners = true; + } + if (executorMultisigOwners[i] == communityMultisig) { + communityMultisigInOwners = true; + } + } + require(timelockInOwners, "timelock not in executorMultisig owners"); + require(communityMultisigInOwners, "communityMultisig not in executorMultisig owners"); + + require(eigenTokenProxyAdmin != beigenTokenProxyAdmin, + "tokens must have different proxy admins to allow different timelock controllers"); + require(eigenTokenTimelockController != beigenTokenTimelockController, + "tokens must have different timelock controllers"); + + // note that proxy admin owners are different but _token_ owners are the same + assertEq(Ownable(address(EIGEN)).owner(), address(eigenTokenTimelockController), + "EIGEN.owner() != eigenTokenTimelockController"); + assertEq(Ownable(address(bEIGEN)).owner(), address(eigenTokenTimelockController), + "bEIGEN.owner() != eigenTokenTimelockController"); + assertEq(eigenTokenProxyAdmin.owner(), address(eigenTokenTimelockController), + "eigenTokenProxyAdmin.owner() != eigenTokenTimelockController"); + assertEq(beigenTokenProxyAdmin.owner(), address(beigenTokenTimelockController), + "beigenTokenProxyAdmin.owner() != beigenTokenTimelockController"); + + assertEq(eigenTokenProxyAdmin.getProxyAdmin(TransparentUpgradeableProxy(payable(address(EIGEN)))), + address(eigenTokenProxyAdmin), + "eigenTokenProxyAdmin is not actually the admin of the EIGEN token"); + assertEq(beigenTokenProxyAdmin.getProxyAdmin(TransparentUpgradeableProxy(payable(address(bEIGEN)))), + address(beigenTokenProxyAdmin), + "beigenTokenProxyAdmin is not actually the admin of the bEIGEN token"); + + require(eigenTokenTimelockController.hasRole(eigenTokenTimelockController.PROPOSER_ROLE(), foundationMultisig), + "foundationMultisig does not have PROPOSER_ROLE on eigenTokenTimelockController"); + require(eigenTokenTimelockController.hasRole(eigenTokenTimelockController.EXECUTOR_ROLE(), foundationMultisig), + "foundationMultisig does not have EXECUTOR_ROLE on eigenTokenTimelockController"); + require(eigenTokenTimelockController.hasRole(eigenTokenTimelockController.CANCELLER_ROLE(), operationsMultisig), + "operationsMultisig does not have CANCELLER_ROLE on eigenTokenTimelockController"); + require(eigenTokenTimelockController.hasRole(eigenTokenTimelockController.TIMELOCK_ADMIN_ROLE(), executorMultisig), + "executorMultisig does not have TIMELOCK_ADMIN_ROLE on eigenTokenTimelockController"); + require(eigenTokenTimelockController.hasRole(eigenTokenTimelockController.TIMELOCK_ADMIN_ROLE(), address(eigenTokenTimelockController)), + "eigenTokenTimelockController does not have TIMELOCK_ADMIN_ROLE on itself"); + + require(beigenTokenTimelockController.hasRole(beigenTokenTimelockController.PROPOSER_ROLE(), foundationMultisig), + "foundationMultisig does not have PROPOSER_ROLE on beigenTokenTimelockController"); + require(beigenTokenTimelockController.hasRole(beigenTokenTimelockController.EXECUTOR_ROLE(), foundationMultisig), + "foundationMultisig does not have EXECUTOR_ROLE on beigenTokenTimelockController"); + require(beigenTokenTimelockController.hasRole(beigenTokenTimelockController.CANCELLER_ROLE(), operationsMultisig), + "operationsMultisig does not have CANCELLER_ROLE on beigenTokenTimelockController"); + require(beigenTokenTimelockController.hasRole(beigenTokenTimelockController.TIMELOCK_ADMIN_ROLE(), executorMultisig), + "executorMultisig does not have TIMELOCK_ADMIN_ROLE on beigenTokenTimelockController"); + require(beigenTokenTimelockController.hasRole(beigenTokenTimelockController.TIMELOCK_ADMIN_ROLE(), address(beigenTokenTimelockController)), + "beigenTokenTimelockController does not have TIMELOCK_ADMIN_ROLE on itself"); + } + + function checkGovernanceConfiguration_WithProtocolCouncil() public { + assertEq(eigenLayerProxyAdmin.owner(), executorMultisig, + "eigenLayerProxyAdmin.owner() != executorMultisig"); + assertEq(delegationManager.owner(), executorMultisig, + "delegationManager.owner() != executorMultisig"); + assertEq(strategyManager.owner(), executorMultisig, + "strategyManager.owner() != executorMultisig"); + assertEq(strategyManager.strategyWhitelister(), address(strategyFactory), + "strategyManager.strategyWhitelister() != address(strategyFactory)"); + assertEq(strategyFactory.owner(), operationsMultisig, + "strategyFactory.owner() != operationsMultisig"); + assertEq(avsDirectory.owner(), executorMultisig, + "avsDirectory.owner() != executorMultisig"); + assertEq(rewardsCoordinator.owner(), operationsMultisig, + "rewardsCoordinator.owner() != operationsMultisig"); + assertEq(eigenLayerPauserReg.unpauser(), executorMultisig, + "eigenLayerPauserReg.unpauser() != operationsMultisig"); + require(eigenLayerPauserReg.isPauser(operationsMultisig), + "operationsMultisig does not have pausing permissions"); + require(eigenLayerPauserReg.isPauser(executorMultisig), + "executorMultisig does not have pausing permissions"); + require(eigenLayerPauserReg.isPauser(pauserMultisig), + "pauserMultisig does not have pausing permissions"); + + // TODO: delete this? it should no longer matter going forwards + address timelockAdmin = NoDelayTimelock(payable(timelock)).admin(); + assertEq(timelockAdmin, operationsMultisig, + "timelockAdmin != operationsMultisig"); + + require(eigenTokenProxyAdmin != beigenTokenProxyAdmin, + "tokens must have different proxy admins to allow different timelock controllers"); + require(protocolTimelockController != protocolTimelockController_BEIGEN, + "tokens must have different timelock controllers"); + + // note that proxy admin owners are different but _token_ owners per se are the same + assertEq(Ownable(address(EIGEN)).owner(), address(executorMultisig), + "EIGEN.owner() != executorMultisig"); + assertEq(Ownable(address(bEIGEN)).owner(), address(executorMultisig), + "bEIGEN.owner() != executorMultisig"); + assertEq(eigenLayerProxyAdmin.owner(), address(executorMultisig), + "eigenLayerProxyAdmin.owner() != executorMultisig"); + assertEq(beigenTokenProxyAdmin.owner(), address(protocolTimelockController_BEIGEN), + "beigenTokenProxyAdmin.owner() != protocolTimelockController_BEIGEN"); + + assertEq(eigenLayerProxyAdmin.getProxyAdmin(TransparentUpgradeableProxy(payable(address(EIGEN)))), + address(eigenLayerProxyAdmin), + "eigenLayerProxyAdmin is not actually the admin of the EIGEN token"); + assertEq(beigenTokenProxyAdmin.getProxyAdmin(TransparentUpgradeableProxy(payable(address(bEIGEN)))), + address(beigenTokenProxyAdmin), + "beigenTokenProxyAdmin is not actually the admin of the bEIGEN token"); + + // check that community multisig and protocol timelock are the owners of the executorMultisig + checkExecutorMultisigOwnership(executorMultisig, address(protocolTimelockController)); + // check that community multisig and bEIGEN protocol timelock are the owners of the beigenExecutorMultisig + checkExecutorMultisigOwnership(beigenExecutorMultisig, address(protocolTimelockController_BEIGEN)); + + checkTimelockControllerConfig(protocolTimelockController); + checkTimelockControllerConfig(protocolTimelockController_BEIGEN); + } + + function checkExecutorMultisigOwnership(address _executorMultisig, address timelockControllerAddress) public view { + (bool success, bytes memory returndata) = _executorMultisig.staticcall(abi.encodeWithSignature("getOwners()")); + require(success, "call to _executorMultisig.getOwners() failed"); + address[] memory _executorMultisigOwners = abi.decode(returndata, (address[])); + require(_executorMultisigOwners.length == 2, + "executorMultisig owners wrong length"); + bool timelockControllerInOwners; + bool communityMultisigInOwners; + for (uint256 i = 0; i < 2; ++i) { + if (_executorMultisigOwners[i] == address(timelockControllerAddress)) { + timelockControllerInOwners = true; + } + if (_executorMultisigOwners[i] == communityMultisig) { + communityMultisigInOwners = true; + } + } + require(timelockControllerInOwners, "timelockControllerAddress not in _executorMultisig owners"); + require(communityMultisigInOwners, "communityMultisig not in _executorMultisig owners"); + + } + + function checkTimelockControllerConfig(TimelockController timelockController) public view { + // check for proposer + executor rights on Protocol Council multisig + require(timelockController.hasRole(timelockController.PROPOSER_ROLE(), protocolCouncilMultisig), + "protocolCouncilMultisig does not have PROPOSER_ROLE on timelockController"); + require(timelockController.hasRole(timelockController.EXECUTOR_ROLE(), protocolCouncilMultisig), + "protocolCouncilMultisig does not have EXECUTOR_ROLE on timelockController"); + + // check for proposer + canceller rights on ops multisig + require(timelockController.hasRole(timelockController.PROPOSER_ROLE(), operationsMultisig), + "operationsMultisig does not have PROPOSER_ROLE on timelockController"); + require(timelockController.hasRole(timelockController.CANCELLER_ROLE(), operationsMultisig), + "operationsMultisig does not have CANCELLER_ROLE on timelockController"); + + // check that community multisig has admin rights + require(timelockController.hasRole(timelockController.TIMELOCK_ADMIN_ROLE(), communityMultisig), + "communityMultisig does not have TIMELOCK_ADMIN_ROLE on timelockController"); + + // check for self-administration + require(timelockController.hasRole(timelockController.TIMELOCK_ADMIN_ROLE(), address(timelockController)), + "timelockController does not have TIMELOCK_ADMIN_ROLE on itself"); + + // check that deployer has no rights + require(!timelockController.hasRole(timelockController.TIMELOCK_ADMIN_ROLE(), msg.sender), + "deployer erroenously retains TIMELOCK_ADMIN_ROLE on timelockController"); + require(!timelockController.hasRole(timelockController.PROPOSER_ROLE(), msg.sender), + "deployer erroenously retains PROPOSER_ROLE on timelockController"); + require(!timelockController.hasRole(timelockController.EXECUTOR_ROLE(), msg.sender), + "deployer erroenously retains EXECUTOR_ROLE on timelockController"); + require(!timelockController.hasRole(timelockController.CANCELLER_ROLE(), msg.sender), + "deployer erroenously retains CANCELLER_ROLE on timelockController"); + } + + // forge script script/utils/CurrentConfigCheck.s.sol:CurrentConfigCheck -vvv --sig "simulateProtocolCouncilUpgrade(string)" $NETWORK_NAME + function simulateProtocolCouncilUpgrade(string memory networkName) public virtual { + run(networkName); + + // deployment steps + deployTimelockControllers(); + deployBeigenExecutorMultisig(); + configureTimelockController(protocolTimelockController); + configureTimelockController(protocolTimelockController_BEIGEN); + + // simulate transferring powers + simulateLegacyTimelockActions(); + simulateEigenTokenTimelockControllerActions(); + simulateBEIGENTokenTimelockControllerActions(); + + // check correctness after deployment + simulation + checkGovernanceConfiguration_WithProtocolCouncil(); + } + + function deployTimelockControllers() public { + // set up initially with sender as a proposer & executor, to be renounced prior to finalizing deployment + address[] memory proposers = new address[](1); + proposers[0] = msg.sender; + + address[] memory executors = new address[](1); + executors[0] = msg.sender; + + vm.startBroadcast(); + protocolTimelockController = + new TimelockController( + 0, // no delay for setup + proposers, + executors + ); + protocolTimelockController_BEIGEN = + new TimelockController( + 0, // no delay for setup + proposers, + executors + ); + vm.stopBroadcast(); + } + + function deployBeigenExecutorMultisig() public { + require(address(beigenTokenTimelockController) != address(0), + "must deploy beigenTokenTimelockController before beigenExecutorMultisig"); + // TODO: solution for local network? + // TODO: verify this also works for Holesky + address safeFactory = 0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2; + address safeSingleton = 0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552; + address safeFallbackHandler = 0xfd0732Dc9E303f09fCEf3a7388Ad10A83459Ec99; + address[] memory owners = new address[](2); + bytes memory emptyData; + owners[0] = address(protocolTimelockController_BEIGEN); + owners[1] = communityMultisig; + bytes memory initializerData = abi.encodeWithSignature( + "setup(address[],uint256,address,bytes,address,address,uint256,address)", + owners, + 1, /* threshold */ + address(0), /* to (used in setupModules) */ + emptyData, /* data (used in setupModules) */ + safeFallbackHandler, + address(0), /* paymentToken */ + 0, /* payment */ + payable(address(0)) /* paymentReceiver */ + ); + uint256 salt = 0; + + bytes memory calldataToFactory = abi.encodeWithSignature( + "createProxyWithNonce(address,bytes,uint256)", + safeSingleton, + initializerData, + salt + ); + + (bool success, bytes memory returndata) = safeFactory.call(calldataToFactory); + require(success, "multisig deployment failed"); + beigenExecutorMultisig = abi.decode(returndata, (address)); + require(beigenExecutorMultisig != address(0), "something wrong in multisig deployment, zero address returned"); + } + + function configureTimelockController(TimelockController timelockController) public { + vm.startBroadcast(); + uint256 tx_array_length = 10; + address[] memory targets = new address[](tx_array_length); + for (uint256 i = 0; i < targets.length; ++i) { + targets[i] = address(timelockController); + } + + uint256[] memory values = new uint256[](tx_array_length); + + bytes[] memory payloads = new bytes[](tx_array_length); + // 1. remove sender as canceller + payloads[0] = abi.encodeWithSelector(AccessControl.revokeRole.selector, timelockController.CANCELLER_ROLE(), msg.sender); + // 2. remove sender as executor + payloads[1] = abi.encodeWithSelector(AccessControl.revokeRole.selector, timelockController.EXECUTOR_ROLE(), msg.sender); + // 3. remove sender as proposer + payloads[2] = abi.encodeWithSelector(AccessControl.revokeRole.selector, timelockController.PROPOSER_ROLE(), msg.sender); + // 4. remove sender as admin + payloads[3] = abi.encodeWithSelector(AccessControl.revokeRole.selector, timelockController.TIMELOCK_ADMIN_ROLE(), msg.sender); + + // 5. add operationsMultisig as canceller + payloads[4] = abi.encodeWithSelector(AccessControl.grantRole.selector, timelockController.CANCELLER_ROLE(), operationsMultisig); + // 6. add operationsMultisig as proposer + payloads[5] = abi.encodeWithSelector(AccessControl.grantRole.selector, timelockController.PROPOSER_ROLE(), operationsMultisig); + + // 7. add protocolCouncilMultisig as proposer + payloads[6] = abi.encodeWithSelector(AccessControl.grantRole.selector, timelockController.PROPOSER_ROLE(), protocolCouncilMultisig); + // 8. add protocolCouncilMultisig as executor + payloads[7] = abi.encodeWithSelector(AccessControl.grantRole.selector, timelockController.EXECUTOR_ROLE(), protocolCouncilMultisig); + + // 9. add communityMultisig as admin + payloads[8] = abi.encodeWithSelector(AccessControl.grantRole.selector, timelockController.TIMELOCK_ADMIN_ROLE(), communityMultisig); + + // TODO: get appropriate value for chain instead of hardcoding + uint256 delayToSet; + if (timelockController == protocolTimelockController) { + delayToSet = 10 days; + } else if (timelockController == protocolTimelockController_BEIGEN) { + delayToSet = 14 days; + } + require(delayToSet != 0, "delay not calculated"); + // 10. set min delay to appropriate length + payloads[9] = abi.encodeWithSelector(timelockController.updateDelay.selector, delayToSet); + + // schedule the batch + timelockController.scheduleBatch( + targets, + values, + payloads, + bytes32(0), // no predecessor needed + bytes32(0), // no salt + 0 // 0 enforced delay + ); + + // execute the batch + timelockController.executeBatch( + targets, + values, + payloads, + bytes32(0), // no predecessor needed + bytes32(0) // no salt + ); + vm.stopBroadcast(); + } + + function simulateLegacyTimelockActions() public { + // TODO + // give proxy admin ownership to timelock controller + // eigenTokenProxyAdmin.transferOwnership(address(timelockController)); + + // swapOwner(address previousOwner, address oldOwner, address newOwner) + // TODO: figure out if this is the correct input for all chains or the communityMultisig is correct on some + address previousOwner = address(1); + // address previousOwner = communityMultisig; + bytes memory data_swapTimelockToProtocolTimelock = + abi.encodeWithSignature("swapOwner(address,address,address)", previousOwner, address(timelock), address(protocolTimelockController)); + bytes memory callToExecutor = encodeForExecutor({ + from: timelock, + to: address(executorMultisig), + value: 0, + data: data_swapTimelockToProtocolTimelock, + operation: ISafe.Operation.Call + }); + // TODO: get appropriate value for chain instead of hardcoding + // uint256 timelockEta = block.timestamp + timelock.delay(); + uint256 timelockEta = block.timestamp + 10 days; + (bytes memory calldata_to_timelock_queuing_action, bytes memory calldata_to_timelock_executing_action) = + encodeForTimelock({ + to: address(executorMultisig), + value: 0, + data: callToExecutor, + timelockEta: timelockEta + }); + vm.startPrank(operationsMultisig); + + (bool success, /*bytes memory returndata*/) = timelock.call(calldata_to_timelock_queuing_action); + require(success, "call to timelock queuing action 1 failed"); + + vm.warp(timelockEta); + (success, /*bytes memory returndata*/) = timelock.call(calldata_to_timelock_executing_action); + require(success, "call to timelock executing action 1 failed"); + + vm.stopPrank(); + } + + function simulateEigenTokenTimelockControllerActions() public { + vm.startPrank(foundationMultisig); + + uint256 tx_array_length = 3; + address[] memory targets = new address[](tx_array_length); + uint256[] memory values = new uint256[](tx_array_length); + bytes[] memory payloads = new bytes[](tx_array_length); + // 1. transfer upgrade rights over EIGEN token from eigenTokenProxyAdmin to eigenLayerProxyAdmin + targets[0] = address(eigenTokenProxyAdmin); + payloads[0] = abi.encodeWithSelector(ProxyAdmin.changeProxyAdmin.selector, address(EIGEN), address(eigenLayerProxyAdmin)); + // 2. transfer ownership of EIGEN token to executorMultisig + targets[1] = address(EIGEN); + payloads[1] = abi.encodeWithSelector(Ownable.transferOwnership.selector, executorMultisig); + // 3. transfer ownership of bEIGEN token to executorMultisig + targets[2] = address(bEIGEN); + payloads[2] = abi.encodeWithSelector(Ownable.transferOwnership.selector, executorMultisig); + + // schedule the batch + uint256 minDelay = eigenTokenTimelockController.getMinDelay(); + eigenTokenTimelockController.scheduleBatch( + targets, + values, + payloads, + bytes32(0), // no predecessor needed + bytes32(0), // no salt + minDelay + ); + + vm.warp(block.timestamp + minDelay); + + // execute the batch + eigenTokenTimelockController.executeBatch( + targets, + values, + payloads, + bytes32(0), // no predecessor needed + bytes32(0) // no salt + ); + + vm.stopPrank(); + } + + function simulateBEIGENTokenTimelockControllerActions() public { + vm.startPrank(foundationMultisig); + + uint256 tx_array_length = 1; + address[] memory targets = new address[](tx_array_length); + uint256[] memory values = new uint256[](tx_array_length); + bytes[] memory payloads = new bytes[](tx_array_length); + // 1. transfer ownership rights over beigenTokenProxyAdmin to protocolTimelockController_BEIGEN + targets[0] = address(beigenTokenProxyAdmin); + payloads[0] = abi.encodeWithSelector(Ownable.transferOwnership.selector, address(protocolTimelockController_BEIGEN)); + + // schedule the batch + uint256 minDelay = beigenTokenTimelockController.getMinDelay(); + beigenTokenTimelockController.scheduleBatch( + targets, + values, + payloads, + bytes32(0), // no predecessor needed + bytes32(0), // no salt + minDelay + ); + + vm.warp(block.timestamp + minDelay); + + // execute the batch + beigenTokenTimelockController.executeBatch( + targets, + values, + payloads, + bytes32(0), // no predecessor needed + bytes32(0) // no salt + ); + + vm.stopPrank(); + } +} diff --git a/script/utils/ExistingDeploymentParser.sol b/script/utils/ExistingDeploymentParser.sol index 0ee3ae863..e205dc739 100644 --- a/script/utils/ExistingDeploymentParser.sol +++ b/script/utils/ExistingDeploymentParser.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.12; import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; +import "@openzeppelin/contracts/governance/TimelockController.sol"; import "../../src/contracts/core/StrategyManager.sol"; import "../../src/contracts/core/Slasher.sol"; @@ -66,13 +67,16 @@ contract ExistingDeploymentParser is Script, Test { StrategyBase public strategyFactoryBeaconImplementation; // Token - ProxyAdmin public tokenProxyAdmin; + ProxyAdmin public eigenTokenProxyAdmin; + ProxyAdmin public beigenTokenProxyAdmin; IEigen public EIGEN; IEigen public EIGENImpl; IBackingEigen public bEIGEN; IBackingEigen public bEIGENImpl; EigenStrategy public eigenStrategy; EigenStrategy public eigenStrategyImpl; + TimelockController public eigenTokenTimelockController; + TimelockController public beigenTokenTimelockController; // EigenPods deployed address[] public multiValidatorPods; @@ -87,6 +91,7 @@ contract ExistingDeploymentParser is Script, Test { address operationsMultisig; address communityMultisig; address pauserMultisig; + address foundationMultisig; address timelock; // strategies deployed @@ -138,87 +143,96 @@ contract ExistingDeploymentParser is Script, Test { string memory existingDeploymentData = vm.readFile(existingDeploymentInfoPath); // check that the chainID matches the one in the config - uint256 configChainId = stdJson.readUint(existingDeploymentData, ".chainInfo.chainId"); + uint256 configChainId = stdJson.readUint(existingDeploymentData, ".config.environment.chainid"); require(configChainId == currentChainId, "You are on the wrong chain for this config"); emit log_named_string("Using addresses file", existingDeploymentInfoPath); - emit log_named_string("- Last Updated", stdJson.readString(existingDeploymentData, ".lastUpdated")); + emit log_named_string("- Last Updated", stdJson.readString(existingDeploymentData, ".config.environment.lastUpdated")); // read all of the deployed addresses - executorMultisig = stdJson.readAddress(existingDeploymentData, ".parameters.executorMultisig"); - operationsMultisig = stdJson.readAddress(existingDeploymentData, ".parameters.operationsMultisig"); - communityMultisig = stdJson.readAddress(existingDeploymentData, ".parameters.communityMultisig"); - pauserMultisig = stdJson.readAddress(existingDeploymentData, ".parameters.pauserMultisig"); - timelock = stdJson.readAddress(existingDeploymentData, ".parameters.timelock"); + executorMultisig = stdJson.readAddress(existingDeploymentData, ".deployment.admin.executorMultisig"); + operationsMultisig = stdJson.readAddress(existingDeploymentData, ".deployment.admin.operationsMultisig"); + communityMultisig = stdJson.readAddress(existingDeploymentData, ".deployment.admin.communityMultisig"); + pauserMultisig = stdJson.readAddress(existingDeploymentData, ".deployment.admin.pauserMultisig"); + foundationMultisig = stdJson.readAddress(existingDeploymentData, ".deployment.admin.foundationMultisig"); + timelock = stdJson.readAddress(existingDeploymentData, ".deployment.admin.timelock"); eigenLayerProxyAdmin = ProxyAdmin( - stdJson.readAddress(existingDeploymentData, ".addresses.eigenLayerProxyAdmin") + stdJson.readAddress(existingDeploymentData, ".deployment.admin.proxyAdmin") ); eigenLayerPauserReg = PauserRegistry( - stdJson.readAddress(existingDeploymentData, ".addresses.eigenLayerPauserReg") + stdJson.readAddress(existingDeploymentData, ".deployment.admin.pauserRegistry") ); - slasher = Slasher(stdJson.readAddress(existingDeploymentData, ".addresses.slasher")); + slasher = Slasher(stdJson.readAddress(existingDeploymentData, ".deployment.core.slasher.proxy")); slasherImplementation = Slasher( - stdJson.readAddress(existingDeploymentData, ".addresses.slasherImplementation") + stdJson.readAddress(existingDeploymentData, ".deployment.core.slasher.impl") ); delegationManager = DelegationManager( - stdJson.readAddress(existingDeploymentData, ".addresses.delegationManager") + stdJson.readAddress(existingDeploymentData, ".deployment.core.delegationManager.proxy") ); delegationManagerImplementation = DelegationManager( - stdJson.readAddress(existingDeploymentData, ".addresses.delegationManagerImplementation") + stdJson.readAddress(existingDeploymentData, ".deployment.core.delegationManager.impl") ); - avsDirectory = AVSDirectory(stdJson.readAddress(existingDeploymentData, ".addresses.avsDirectory")); + avsDirectory = AVSDirectory(stdJson.readAddress(existingDeploymentData, ".deployment.core.avsDirectory.proxy")); avsDirectoryImplementation = AVSDirectory( - stdJson.readAddress(existingDeploymentData, ".addresses.avsDirectoryImplementation") + stdJson.readAddress(existingDeploymentData, ".deployment.core.avsDirectory.impl") ); rewardsCoordinator = RewardsCoordinator( - stdJson.readAddress(existingDeploymentData, ".addresses.rewardsCoordinator") + stdJson.readAddress(existingDeploymentData, ".deployment.core.rewardsCoordinator.proxy") ); rewardsCoordinatorImplementation = RewardsCoordinator( - stdJson.readAddress(existingDeploymentData, ".addresses.rewardsCoordinatorImplementation") + stdJson.readAddress(existingDeploymentData, ".deployment.core.rewardsCoordinator.impl") ); - strategyManager = StrategyManager(stdJson.readAddress(existingDeploymentData, ".addresses.strategyManager")); + strategyManager = StrategyManager(stdJson.readAddress(existingDeploymentData, ".deployment.core.strategyManager.proxy")); strategyManagerImplementation = StrategyManager( - stdJson.readAddress(existingDeploymentData, ".addresses.strategyManagerImplementation") + stdJson.readAddress(existingDeploymentData, ".deployment.core.strategyManager.impl") ); - strategyFactory = StrategyFactory(stdJson.readAddress(existingDeploymentData, ".addresses.strategyFactory")); + strategyFactory = StrategyFactory(stdJson.readAddress(existingDeploymentData, ".deployment.strategies.strategyFactory.proxy")); strategyFactoryImplementation = StrategyFactory( - stdJson.readAddress(existingDeploymentData, ".addresses.strategyFactoryImplementation") + stdJson.readAddress(existingDeploymentData, ".deployment.strategies.strategyFactory.impl") ); - eigenPodManager = EigenPodManager(stdJson.readAddress(existingDeploymentData, ".addresses.eigenPodManager")); + eigenPodManager = EigenPodManager(stdJson.readAddress(existingDeploymentData, ".deployment.pods.eigenPodManager.proxy")); eigenPodManagerImplementation = EigenPodManager( - stdJson.readAddress(existingDeploymentData, ".addresses.eigenPodManagerImplementation") + stdJson.readAddress(existingDeploymentData, ".deployment.pods.eigenPodManager.impl") ); - eigenPodBeacon = UpgradeableBeacon(stdJson.readAddress(existingDeploymentData, ".addresses.eigenPodBeacon")); + eigenPodBeacon = UpgradeableBeacon(stdJson.readAddress(existingDeploymentData, ".deployment.pods.eigenPod.beacon")); eigenPodImplementation = EigenPod( - payable(stdJson.readAddress(existingDeploymentData, ".addresses.eigenPodImplementation")) + payable(stdJson.readAddress(existingDeploymentData, ".deployment.pods.eigenPod.impl")) ); baseStrategyImplementation = StrategyBase( - stdJson.readAddress(existingDeploymentData, ".addresses.baseStrategyImplementation") + stdJson.readAddress(existingDeploymentData, ".deployment.strategies.preLongtailStrats.impl") ); - emptyContract = EmptyContract(stdJson.readAddress(existingDeploymentData, ".addresses.emptyContract")); + emptyContract = EmptyContract(stdJson.readAddress(existingDeploymentData, ".deployment.emptyContract")); + // TODO: this is now broken, potentially fix it // Strategies Deployed, load strategy list - numStrategiesDeployed = stdJson.readUint(existingDeploymentData, ".addresses.numStrategiesDeployed"); - for (uint256 i = 0; i < numStrategiesDeployed; ++i) { - // Form the key for the current element - string memory key = string.concat(".addresses.strategyAddresses[", vm.toString(i), "]"); - - // Use the key and parse the strategy address - address strategyAddress = abi.decode(stdJson.parseRaw(existingDeploymentData, key), (address)); - deployedStrategyArray.push(StrategyBase(strategyAddress)); - } + // numStrategiesDeployed = stdJson.readUint(existingDeploymentData, ".addresses.numStrategiesDeployed"); + // for (uint256 i = 0; i < numStrategiesDeployed; ++i) { + // // Form the key for the current element + // string memory key = string.concat(".addresses.strategyAddresses[", vm.toString(i), "]"); + + // // Use the key and parse the strategy address + // address strategyAddress = abi.decode(stdJson.parseRaw(existingDeploymentData, key), (address)); + // deployedStrategyArray.push(StrategyBase(strategyAddress)); + // } // token - tokenProxyAdmin = ProxyAdmin(stdJson.readAddress(existingDeploymentData, ".addresses.token.tokenProxyAdmin")); - EIGEN = IEigen(stdJson.readAddress(existingDeploymentData, ".addresses.token.EIGEN")); - EIGENImpl = IEigen(stdJson.readAddress(existingDeploymentData, ".addresses.token.EIGENImpl")); - bEIGEN = IBackingEigen(stdJson.readAddress(existingDeploymentData, ".addresses.token.bEIGEN")); - bEIGENImpl = IBackingEigen(stdJson.readAddress(existingDeploymentData, ".addresses.token.bEIGENImpl")); - eigenStrategy = EigenStrategy(stdJson.readAddress(existingDeploymentData, ".addresses.token.eigenStrategy")); + eigenTokenProxyAdmin = ProxyAdmin(stdJson.readAddress(existingDeploymentData, ".deployment.token.EIGEN.proxyAdmin")); + beigenTokenProxyAdmin = ProxyAdmin(stdJson.readAddress(existingDeploymentData, ".deployment.token.bEIGEN.proxyAdmin")); + EIGEN = IEigen(stdJson.readAddress(existingDeploymentData, ".deployment.token.EIGEN.proxy")); + EIGENImpl = IEigen(stdJson.readAddress(existingDeploymentData, ".deployment.token.EIGEN.impl")); + bEIGEN = IBackingEigen(stdJson.readAddress(existingDeploymentData, ".deployment.token.bEIGEN.proxy")); + bEIGENImpl = IBackingEigen(stdJson.readAddress(existingDeploymentData, ".deployment.token.bEIGEN.impl")); + eigenStrategy = EigenStrategy(stdJson.readAddress(existingDeploymentData, ".deployment.token.eigenStrategy.proxy")); eigenStrategyImpl = EigenStrategy( - stdJson.readAddress(existingDeploymentData, ".addresses.token.eigenStrategyImpl") + stdJson.readAddress(existingDeploymentData, ".deployment.token.eigenStrategy.impl") ); + eigenTokenTimelockController = TimelockController( + payable(stdJson.readAddress(existingDeploymentData, ".deployment.token.EIGEN.timelockController"))); + beigenTokenTimelockController = TimelockController( + payable(stdJson.readAddress(existingDeploymentData, ".deployment.token.bEIGEN.timelockController"))); + + } function _parseDeployedEigenPods( @@ -230,7 +244,7 @@ contract ExistingDeploymentParser is Script, Test { string memory existingDeploymentData = vm.readFile(existingDeploymentInfoPath); // check that the chainID matches the one in the config - uint256 configChainId = stdJson.readUint(existingDeploymentData, ".chainInfo.chainId"); + uint256 configChainId = stdJson.readUint(existingDeploymentData, ".config.environment.chainid"); require(configChainId == currentChainId, "You are on the wrong chain for this config"); multiValidatorPods = stdJson.readAddressArray(existingDeploymentData, ".eigenPods.multiValidatorPods"); @@ -256,98 +270,101 @@ contract ExistingDeploymentParser is Script, Test { string memory initialDeploymentData = vm.readFile(initialDeploymentParamsPath); // check that the chainID matches the one in the config - uint256 configChainId = stdJson.readUint(initialDeploymentData, ".chainInfo.chainId"); + uint256 configChainId = stdJson.readUint(initialDeploymentData, ".config.environment.chainid"); require(configChainId == currentChainId, "You are on the wrong chain for this config"); emit log_named_string("Using config file", initialDeploymentParamsPath); - emit log_named_string("- Last Updated", stdJson.readString(initialDeploymentData, ".lastUpdated")); + emit log_named_string("- Last Updated", stdJson.readString(initialDeploymentData, ".config.environment.lastUpdated")); // read all of the deployed addresses - executorMultisig = stdJson.readAddress(initialDeploymentData, ".multisig_addresses.executorMultisig"); - operationsMultisig = stdJson.readAddress(initialDeploymentData, ".multisig_addresses.operationsMultisig"); - communityMultisig = stdJson.readAddress(initialDeploymentData, ".multisig_addresses.communityMultisig"); - pauserMultisig = stdJson.readAddress(initialDeploymentData, ".multisig_addresses.pauserMultisig"); - + executorMultisig = stdJson.readAddress(initialDeploymentData, ".deployment.admin.executorMultisig"); + operationsMultisig = stdJson.readAddress(initialDeploymentData, ".deployment.admin.operationsMultisig"); + communityMultisig = stdJson.readAddress(initialDeploymentData, ".deployment.admin.communityMultisig"); + pauserMultisig = stdJson.readAddress(initialDeploymentData, ".deployment.admin.pauserMultisig"); + foundationMultisig = stdJson.readAddress(initialDeploymentData, ".deployment.admin.foundationMultisig"); + timelock = stdJson.readAddress(initialDeploymentData, ".deployment.admin.timelock"); + + // TODO: this is now broken // Strategies to Deploy, load strategy list - numStrategiesToDeploy = stdJson.readUint(initialDeploymentData, ".strategies.numStrategies"); - STRATEGY_MAX_PER_DEPOSIT = stdJson.readUint(initialDeploymentData, ".strategies.MAX_PER_DEPOSIT"); - STRATEGY_MAX_TOTAL_DEPOSITS = stdJson.readUint(initialDeploymentData, ".strategies.MAX_TOTAL_DEPOSITS"); - for (uint256 i = 0; i < numStrategiesToDeploy; ++i) { - // Form the key for the current element - string memory key = string.concat(".strategies.strategiesToDeploy[", vm.toString(i), "]"); + // numStrategiesToDeploy = stdJson.readUint(initialDeploymentData, ".strategies.numStrategies"); + // STRATEGY_MAX_PER_DEPOSIT = stdJson.readUint(initialDeploymentData, ".strategies.MAX_PER_DEPOSIT"); + // STRATEGY_MAX_TOTAL_DEPOSITS = stdJson.readUint(initialDeploymentData, ".strategies.MAX_TOTAL_DEPOSITS"); + // for (uint256 i = 0; i < numStrategiesToDeploy; ++i) { + // // Form the key for the current element + // string memory key = string.concat(".strategies.strategiesToDeploy[", vm.toString(i), "]"); - // Use parseJson with the key to get the value for the current element - bytes memory tokenInfoBytes = stdJson.parseRaw(initialDeploymentData, key); + // // Use parseJson with the key to get the value for the current element + // bytes memory tokenInfoBytes = stdJson.parseRaw(initialDeploymentData, key); - // Decode the token information into the Token struct - StrategyUnderlyingTokenConfig memory tokenInfo = abi.decode( - tokenInfoBytes, - (StrategyUnderlyingTokenConfig) - ); + // // Decode the token information into the Token struct + // StrategyUnderlyingTokenConfig memory tokenInfo = abi.decode( + // tokenInfoBytes, + // (StrategyUnderlyingTokenConfig) + // ); - strategiesToDeploy.push(tokenInfo); - } + // strategiesToDeploy.push(tokenInfo); + // } // Read initialize params for upgradeable contracts - STRATEGY_MANAGER_INIT_PAUSED_STATUS = stdJson.readUint( - initialDeploymentData, - ".strategyManager.init_paused_status" - ); - STRATEGY_MANAGER_WHITELISTER = stdJson.readAddress( - initialDeploymentData, - ".strategyManager.init_strategy_whitelister" - ); - // Slasher - SLASHER_INIT_PAUSED_STATUS = stdJson.readUint(initialDeploymentData, ".slasher.init_paused_status"); - // DelegationManager - DELEGATION_MANAGER_MIN_WITHDRAWAL_DELAY_BLOCKS = stdJson.readUint( - initialDeploymentData, - ".delegationManager.init_minWithdrawalDelayBlocks" - ); - DELEGATION_MANAGER_INIT_PAUSED_STATUS = stdJson.readUint( - initialDeploymentData, - ".delegationManager.init_paused_status" - ); - // RewardsCoordinator - REWARDS_COORDINATOR_INIT_PAUSED_STATUS = stdJson.readUint( - initialDeploymentData, - ".rewardsCoordinator.init_paused_status" - ); - REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS = uint32( - stdJson.readUint(initialDeploymentData, ".rewardsCoordinator.CALCULATION_INTERVAL_SECONDS") - ); - REWARDS_COORDINATOR_MAX_REWARDS_DURATION = uint32( - stdJson.readUint(initialDeploymentData, ".rewardsCoordinator.MAX_REWARDS_DURATION") - ); - REWARDS_COORDINATOR_MAX_RETROACTIVE_LENGTH = uint32( - stdJson.readUint(initialDeploymentData, ".rewardsCoordinator.MAX_RETROACTIVE_LENGTH") - ); - REWARDS_COORDINATOR_MAX_FUTURE_LENGTH = uint32( - stdJson.readUint(initialDeploymentData, ".rewardsCoordinator.MAX_FUTURE_LENGTH") - ); - REWARDS_COORDINATOR_GENESIS_REWARDS_TIMESTAMP = uint32( - stdJson.readUint(initialDeploymentData, ".rewardsCoordinator.GENESIS_REWARDS_TIMESTAMP") - ); - REWARDS_COORDINATOR_UPDATER = stdJson.readAddress( - initialDeploymentData, - ".rewardsCoordinator.rewards_updater_address" - ); - REWARDS_COORDINATOR_ACTIVATION_DELAY = uint32( - stdJson.readUint(initialDeploymentData, ".rewardsCoordinator.activation_delay") - ); - REWARDS_COORDINATOR_DEFAULT_OPERATOR_SPLIT_BIPS = uint32( - stdJson.readUint(initialDeploymentData, ".rewardsCoordinator.default_operator_split_bips") - ); - // AVSDirectory - AVS_DIRECTORY_INIT_PAUSED_STATUS = stdJson.readUint(initialDeploymentData, ".avsDirectory.init_paused_status"); - // EigenPodManager - EIGENPOD_MANAGER_INIT_PAUSED_STATUS = stdJson.readUint( - initialDeploymentData, - ".eigenPodManager.init_paused_status" - ); - // EigenPod - EIGENPOD_GENESIS_TIME = uint64(stdJson.readUint(initialDeploymentData, ".eigenPod.GENESIS_TIME")); - ETHPOSDepositAddress = stdJson.readAddress(initialDeploymentData, ".ethPOSDepositAddress"); + // STRATEGY_MANAGER_INIT_PAUSED_STATUS = stdJson.readUint( + // initialDeploymentData, + // ".strategyManager.init_paused_status" + // ); + // STRATEGY_MANAGER_WHITELISTER = stdJson.readAddress( + // initialDeploymentData, + // ".strategyManager.init_strategy_whitelister" + // ); + // // Slasher + // SLASHER_INIT_PAUSED_STATUS = stdJson.readUint(initialDeploymentData, ".slasher.init_paused_status"); + // // DelegationManager + // DELEGATION_MANAGER_MIN_WITHDRAWAL_DELAY_BLOCKS = stdJson.readUint( + // initialDeploymentData, + // ".delegationManager.init_minWithdrawalDelayBlocks" + // ); + // DELEGATION_MANAGER_INIT_PAUSED_STATUS = stdJson.readUint( + // initialDeploymentData, + // ".delegationManager.init_paused_status" + // ); + // // RewardsCoordinator + // REWARDS_COORDINATOR_INIT_PAUSED_STATUS = stdJson.readUint( + // initialDeploymentData, + // ".rewardsCoordinator.init_paused_status" + // ); + // REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS = uint32( + // stdJson.readUint(initialDeploymentData, ".rewardsCoordinator.CALCULATION_INTERVAL_SECONDS") + // ); + // REWARDS_COORDINATOR_MAX_REWARDS_DURATION = uint32( + // stdJson.readUint(initialDeploymentData, ".rewardsCoordinator.MAX_REWARDS_DURATION") + // ); + // REWARDS_COORDINATOR_MAX_RETROACTIVE_LENGTH = uint32( + // stdJson.readUint(initialDeploymentData, ".rewardsCoordinator.MAX_RETROACTIVE_LENGTH") + // ); + // REWARDS_COORDINATOR_MAX_FUTURE_LENGTH = uint32( + // stdJson.readUint(initialDeploymentData, ".rewardsCoordinator.MAX_FUTURE_LENGTH") + // ); + // REWARDS_COORDINATOR_GENESIS_REWARDS_TIMESTAMP = uint32( + // stdJson.readUint(initialDeploymentData, ".rewardsCoordinator.GENESIS_REWARDS_TIMESTAMP") + // ); + // REWARDS_COORDINATOR_UPDATER = stdJson.readAddress( + // initialDeploymentData, + // ".rewardsCoordinator.rewards_updater_address" + // ); + // REWARDS_COORDINATOR_ACTIVATION_DELAY = uint32( + // stdJson.readUint(initialDeploymentData, ".rewardsCoordinator.activation_delay") + // ); + // REWARDS_COORDINATOR_DEFAULT_OPERATOR_SPLIT_BIPS = uint32( + // stdJson.readUint(initialDeploymentData, ".rewardsCoordinator.default_operator_split_bips") + // ); + // // AVSDirectory + // AVS_DIRECTORY_INIT_PAUSED_STATUS = stdJson.readUint(initialDeploymentData, ".avsDirectory.init_paused_status"); + // // EigenPodManager + // EIGENPOD_MANAGER_INIT_PAUSED_STATUS = stdJson.readUint( + // initialDeploymentData, + // ".eigenPodManager.init_paused_status" + // ); + // // EigenPod + // EIGENPOD_GENESIS_TIME = uint64(stdJson.readUint(initialDeploymentData, ".eigenPod.GENESIS_TIME")); + ETHPOSDepositAddress = stdJson.readAddress(initialDeploymentData, ".config.params.ethPOS"); logInitialDeploymentParams(); } @@ -754,6 +771,7 @@ contract ExistingDeploymentParser is Script, Test { vm.serializeAddress(parameters, "operationsMultisig", operationsMultisig); vm.serializeAddress(parameters, "communityMultisig", communityMultisig); vm.serializeAddress(parameters, "pauserMultisig", pauserMultisig); + vm.serializeAddress(parameters, "foundationMultisig", foundationMultisig); vm.serializeAddress(parameters, "timelock", timelock); string memory parameters_output = vm.serializeAddress(parameters, "operationsMultisig", operationsMultisig); diff --git a/script/utils/TimelockEncoding.sol b/script/utils/TimelockEncoding.sol new file mode 100644 index 000000000..93e73b4ee --- /dev/null +++ b/script/utils/TimelockEncoding.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.12; + +import "forge-std/Script.sol"; +import "forge-std/Test.sol"; + +import "./TxEncodingInterfaces.sol"; + +contract TimelockEncoding is Test { + // CALLDATA FOR CALL FROM TIMELOCK TO EXECUTOR MULTISIG + uint256 safeTxGas = 0; + uint256 baseGas = 0; + uint256 gasPrice = 0; + address gasToken = address(uint160(0)); + address payable refundReceiver = payable(address(uint160(0))); + + // CALDATA FOR CALL TO TIMELOCK + uint256 timelockValue = 0; + // empty string, just encode all the data in 'timelockData' + string timelockSignature; + + // appropriate address on mainnet, Holesky, and many other chains + address multiSendCallOnly = 0x40A2aCCbd92BCA938b02010E17A5b8929b49130D; + + function encodeForTimelock( + address to, + uint256 value, + bytes memory data, + uint256 timelockEta + ) public returns (bytes memory calldata_to_timelock_queuing_action, bytes memory calldata_to_timelock_executing_action) { + calldata_to_timelock_queuing_action = abi.encodeWithSelector(ITimelock.queueTransaction.selector, + to, + value, + timelockSignature, + data, + timelockEta + ); + + emit log_named_bytes("calldata_to_timelock_queuing_action", calldata_to_timelock_queuing_action); + + calldata_to_timelock_executing_action = abi.encodeWithSelector(ITimelock.executeTransaction.selector, + to, + value, + timelockSignature, + data, + timelockEta + ); + + emit log_named_bytes("calldata_to_timelock_executing_action", calldata_to_timelock_executing_action); + + return (calldata_to_timelock_queuing_action, calldata_to_timelock_executing_action); + } + + function encodeForExecutor( + address from, + address to, + uint256 value, + bytes memory data, + ISafe.Operation operation + ) public returns (bytes memory) { + // encode the "signature" required by the Safe + bytes1 v = bytes1(uint8(1)); + bytes32 r = bytes32(uint256(uint160(from))); + bytes32 s; + bytes memory sig = abi.encodePacked(r,s,v); + emit log_named_bytes("sig", sig); + + bytes memory final_calldata_to_executor_multisig = abi.encodeWithSelector(ISafe.execTransaction.selector, + to, + value, + data, + operation, + safeTxGas, + baseGas, + gasPrice, + gasToken, + refundReceiver, + sig + ); + + emit log_named_bytes("final_calldata_to_executor_multisig", final_calldata_to_executor_multisig); + + return final_calldata_to_executor_multisig; + } + + struct Tx { + address to; + uint256 value; + bytes data; + } + + function encodeMultisendTxs(Tx[] memory txs) public pure returns (bytes memory) { + bytes memory ret = new bytes(0); + for (uint256 i = 0; i < txs.length; i++) { + ret = abi.encodePacked( + ret, + abi.encodePacked( + uint8(0), + txs[i].to, + txs[i].value, + uint256(txs[i].data.length), + txs[i].data + ) + ); + } + return ret; + } +} \ No newline at end of file diff --git a/script/utils/TxEncodingInterfaces.sol b/script/utils/TxEncodingInterfaces.sol new file mode 100644 index 000000000..cbcc8b697 --- /dev/null +++ b/script/utils/TxEncodingInterfaces.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: LGPL AND BSD 3-Clause +pragma solidity >=0.5.0; + +// based on https://github.com/safe-global/safe-smart-account/blob/v1.3.0/contracts/GnosisSafe.sol +interface ISafe { + function execTransaction( + address to, + uint256 value, + bytes calldata data, + uint8 operation, + uint256 safeTxGas, + uint256 baseGas, + uint256 gasPrice, + address gasToken, + address payable refundReceiver, + bytes memory signatures + ) external; + + enum Operation {Call, DelegateCall} +} + +// based on https://github.com/compound-finance/compound-protocol/blob/master/contracts/Timelock.sol +interface ITimelock { + function queueTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) external returns (bytes32); + function executeTransaction(address target, uint value, string memory signature, bytes memory data, uint eta) external payable returns (bytes memory); + function queuedTransactions(bytes32) external view returns (bool); +} \ No newline at end of file diff --git a/src/contracts/interfaces/IDelegationFaucet.sol b/src/contracts/interfaces/IDelegationFaucet.sol deleted file mode 100644 index 8741208db..000000000 --- a/src/contracts/interfaces/IDelegationFaucet.sol +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity >=0.5.0; - -import "src/contracts/interfaces/IDelegationManager.sol"; - -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -interface IDelegationFaucet { - function mintDepositAndDelegate( - address _operator, - IDelegationManager.SignatureWithExpiry memory approverSignatureAndExpiry, - bytes32 approverSalt, - uint256 _depositAmount - ) external; - - function getStaker(address operator) external returns (address); - - function depositIntoStrategy( - address staker, - IStrategy strategy, - IERC20 token, - uint256 amount - ) external returns (bytes memory); - - function queueWithdrawal( - address staker, - IDelegationManager.QueuedWithdrawalParams[] calldata queuedWithdrawalParams - ) external returns (bytes memory); - - function completeQueuedWithdrawal( - address staker, - IDelegationManager.Withdrawal calldata queuedWithdrawal, - IERC20[] calldata tokens, - uint256 middlewareTimesIndex, - bool receiveAsTokens - ) external returns (bytes memory); - - function transfer(address staker, address token, address to, uint256 amount) external returns (bytes memory); - - function callAddress(address to, bytes memory data) external payable returns (bytes memory); -}