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); -}