From 7e8fcf0f3f6d974019a2f177063855878b9af46c Mon Sep 17 00:00:00 2001 From: MinisculeTarantula Date: Mon, 3 Mar 2025 16:43:08 -0800 Subject: [PATCH 1/9] feat: scaffolding for initial gov deployment adds a few fields to zeus env, which seem advisable to have regardless script compiles but will need more work to be complete. several TODOs left in the code to address --- script/releases/Env.sol | 16 + script/releases/v0.0.1-gov/1-eoa.s.sol | 433 ++++++++++++++++++++++++ script/releases/v0.0.1-gov/upgrade.json | 11 + 3 files changed, 460 insertions(+) create mode 100644 script/releases/v0.0.1-gov/1-eoa.s.sol create mode 100644 script/releases/v0.0.1-gov/upgrade.json diff --git a/script/releases/Env.sol b/script/releases/Env.sol index e094d9ae3a..8fa6373d56 100644 --- a/script/releases/Env.sol +++ b/script/releases/Env.sol @@ -57,6 +57,10 @@ library Env { return _envAddress("executorMultisig"); } + function beigenExecutorMultisig() internal view returns (address) { + return _envAddress("beigenExecutorMultisig"); + } + function opsMultisig() internal view returns (address) { return _envAddress("operationsMultisig"); } @@ -69,10 +73,18 @@ library Env { return _envAddress("pauserMultisig"); } + function communityMultisig() internal view returns (address) { + return _envAddress("communityMultisig"); + } + function proxyAdmin() internal view returns (address) { return _envAddress("proxyAdmin"); } + function beigenProxyAdmin() internal view returns (address) { + return _envAddress("beigenProxyAdmin"); + } + function ethPOS() internal view returns (IETHPOSDeposit) { return IETHPOSDeposit(_envAddress("ethPOS")); } @@ -81,6 +93,10 @@ library Env { return TimelockController(payable(_envAddress("timelockController"))); } + function beigenTimelockController() internal view returns (TimelockController) { + return TimelockController(payable(_envAddress("beigenTimelockController"))); + } + function multiSendCallOnly() internal view returns (address) { return _envAddress("MultiSendCallOnly"); } diff --git a/script/releases/v0.0.1-gov/1-eoa.s.sol b/script/releases/v0.0.1-gov/1-eoa.s.sol new file mode 100644 index 0000000000..02aa83799a --- /dev/null +++ b/script/releases/v0.0.1-gov/1-eoa.s.sol @@ -0,0 +1,433 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.12; + +import {EOADeployer} from "zeus-templates/templates/EOADeployer.sol"; +import "../Env.sol"; + +import "@openzeppelin/contracts/governance/TimelockController.sol"; +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; + +import "src/test/mocks/EmptyContract.sol"; + +contract Deploy is EOADeployer { + using Env for *; + + function _runAsEOA() internal override { + vm.startBroadcast(); + + deployTimelockControllers(); + deployProtocolMultisigs(); + configureTimelockController(Env.timelockController()); + configureTimelockController(Env.beigenTimelockController()); + deployTokensAndStrategy(); + + vm.stopBroadcast(); + } + + function testDeploy() public virtual { + _runAsEOA(); + // TODO: seems more appropriate after more complete deployment! + // checkGovernanceConfiguration(); + } + + // set up initially with deployer as a proposer & executor, to be renounced prior to finalizing deployment + function deployTimelockControllers() public { + address[] memory proposers = new address[](1); + proposers[0] = msg.sender; + + address[] memory executors = new address[](1); + executors[0] = msg.sender; + + vm.startBroadcast(); + deployImpl({ + name: "timelockController", + deployedTo: address(new TimelockController({ + minDelay: 0, // no delay for setup + proposers: proposers, + executors: executors, + admin: address(0) + })) + }); + deployImpl({ + name: "beigenTimelockController", + deployedTo: address(new TimelockController({ + minDelay: 0, // no delay for setup + proposers: proposers, + executors: executors, + admin: address(0) + })) + }); + } + + function deployProtocolMultisigs() public { + // deploy multisigs that simply have the deployer as their initial owner + address[] memory singleOwner = new address[](1); + singleOwner[0] = msg.sender; + deployImpl({ + name: "pauserMultisig", + deployedTo: deployMultisig({ + initialOwners: singleOwner, + initialThreshold: 1 + }) + }); + deployImpl({ + name: "opsMultisig", + deployedTo: deployMultisig({ + initialOwners: singleOwner, + initialThreshold: 1 + }) + }); + deployImpl({ + name: "protocolCouncilMultisig", + deployedTo: deployMultisig({ + initialOwners: singleOwner, + initialThreshold: 1 + }) + }); + deployImpl({ + name: "communityMultisig", + deployedTo: deployMultisig({ + initialOwners: singleOwner, + initialThreshold: 1 + }) + }); + + // deploy primary executorMultisig + require(address(Env.timelockController()) != address(0), + "must deploy timelockController before executorMultisig"); + address[] memory owners_executorMultisig = new address[](2); + owners_executorMultisig[0] = address(Env.timelockController()); + owners_executorMultisig[1] = Env.communityMultisig(); + deployImpl({ + name: "executorMultisig", + deployedTo: deployMultisig({ + initialOwners: owners_executorMultisig, + initialThreshold: 1 + }) + }); + + // deploy beigenExecutorMultisig + require(address(Env.beigenTimelockController()) != address(0), + "must deploy beigenTokenTimelockController before beigenExecutorMultisig"); + address[] memory owners_beigenExecutorMultisig = new address[](2); + owners_beigenExecutorMultisig[0] = address(Env.beigenTimelockController()); + owners_beigenExecutorMultisig[1] = Env.communityMultisig(); + deployImpl({ + name: "beigenExecutorMultisig", + deployedTo: deployMultisig({ + initialOwners: owners_beigenExecutorMultisig, + initialThreshold: 1 + }) + }); + } + + function deployMultisig(address[] memory initialOwners, uint256 initialThreshold) public returns (address) { + // TODO: solution for local networks / those that do not have Safe deployed on them? + // addresses taken from https://github.com/safe-global/safe-smart-account/blob/main/CHANGELOG.md#expected-addresses-with-deterministic-deployment-proxy-default + address safeFactory = 0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2; + address safeSingleton = 0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552; + address safeFallbackHandler = 0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4; + + bytes memory emptyData; + // TODO: is implementing a nonzero salt useful at all? if yes, this should be an input + uint256 salt = 0; + + bytes memory initializerData = abi.encodeWithSignature( + "setup(address[],uint256,address,bytes,address,address,uint256,address)", + initialOwners, /* signers */ + initialThreshold, /* threshold */ + address(0), /* to (used in setupModules) */ + emptyData, /* data (used in setupModules) */ + safeFallbackHandler, + address(0), /* paymentToken */ + 0, /* payment */ + payable(address(0)) /* paymentReceiver */ + ); + + 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"); + address deployedMultisig = abi.decode(returndata, (address)); + require(deployedMultisig != address(0), "something wrong in multisig deployment, zero address returned"); + return deployedMultisig; + } + + function configureTimelockController(TimelockController timelockController) public { + 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(), Env.opsMultisig()); + // 6. add operationsMultisig as proposer + payloads[5] = abi.encodeWithSelector(AccessControl.grantRole.selector, timelockController.PROPOSER_ROLE(), Env.opsMultisig()); + + // 7. add protocolCouncilMultisig as proposer + payloads[6] = abi.encodeWithSelector(AccessControl.grantRole.selector, timelockController.PROPOSER_ROLE(), Env.protocolCouncilMultisig()); + // 8. add protocolCouncilMultisig as executor + payloads[7] = abi.encodeWithSelector(AccessControl.grantRole.selector, timelockController.EXECUTOR_ROLE(), Env.protocolCouncilMultisig()); + + // 9. add communityMultisig as admin + payloads[8] = abi.encodeWithSelector(AccessControl.grantRole.selector, timelockController.TIMELOCK_ADMIN_ROLE(), Env.communityMultisig()); + + uint256 delayToSet; + if (block.chainid == 1) { + if (timelockController == Env.timelockController()) { + delayToSet = 10 days; + } else if (timelockController == Env.beigenTimelockController()) { + delayToSet = 24 days; + } else { + revert("error in setting timelock delay"); + } + } else { + delayToSet = 1; + } + 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 + ); + } + + function deployTokensAndStrategy() public { + deployImpl({ + name: "proxyAdmin", + deployedTo: address(new ProxyAdmin()) + }); + deployImpl({ + name: "beigenProxyAdmin", + deployedTo: address(new ProxyAdmin()) + }); + ProxyAdmin proxyAdmin = ProxyAdmin(Env.proxyAdmin()); + ProxyAdmin beigenProxyAdmin = ProxyAdmin(Env.beigenProxyAdmin()); + + // TODO: decide if this should be in env + // placeholder used for initial proxy deployments since initial implementation must be a contract + EmptyContract emptyContract = new EmptyContract(); + + deployProxy({ + name: type(BackingEigen).name, + deployedTo: address(new TransparentUpgradeableProxy({ + _logic: address(emptyContract), + admin_: address(beigenProxyAdmin), + _data: "" + })) + }); + deployProxy({ + name: type(Eigen).name, + deployedTo: address(new TransparentUpgradeableProxy({ + _logic: address(emptyContract), + admin_: address(proxyAdmin), + _data: "" + })) + }); + + deployImpl({ + name: type(BackingEigen).name, + deployedTo: address(new BackingEigen({ + _EIGEN: IERC20(Env.proxy.eigen()) + })) + }); + deployImpl({ + name: type(Eigen).name, + deployedTo: address(new Eigen({ + _bEIGEN: IERC20(Env.proxy.beigen()) + })) + }); + + // use deployer as initial owner, for disabling transfer restrictions prior to transferring ownership + address initialOwner = msg.sender; + address[] memory minters; + uint256[] memory mintingAllowances; + uint256[] memory mintAllowedAfters; + proxyAdmin.upgradeAndCall({ + proxy: ITransparentUpgradeableProxy(address(Env.proxy.eigen())), + implementation: address(Env.impl.eigen()), + data: abi.encodeWithSelector( + Eigen.initialize.selector, + initialOwner, + minters, + mintingAllowances, + mintAllowedAfters + ) + }); + Eigen(address(Env.proxy.eigen())).disableTransferRestrictions(); + Eigen(address(Env.proxy.eigen())).transferOwnership(Env.executorMultisig()); + + // use deployer as initial owner, for editing minting permissions prior to transferring ownership + proxyAdmin.upgradeAndCall({ + proxy: ITransparentUpgradeableProxy(address(Env.proxy.beigen())), + implementation: address(Env.impl.beigen()), + data: abi.encodeWithSelector( + BackingEigen.initialize.selector, + initialOwner + ) + }); + // TODO: get correct minterAddress here! + BackingEigen(address(Env.proxy.beigen())).setIsMinter({ + minterAddress: address(0), + newStatus: true + }); + BackingEigen(address(Env.proxy.beigen())).transferOwnership(Env.beigenExecutorMultisig()); + + proxyAdmin.changeProxyAdmin({ + proxy: ITransparentUpgradeableProxy(address(Env.proxy.eigen())), + newAdmin: address(proxyAdmin) + }); + + proxyAdmin.changeProxyAdmin({ + proxy: ITransparentUpgradeableProxy(address(Env.proxy.beigen())), + newAdmin: address(proxyAdmin) + }); + } + + function checkGovernanceConfiguration() public view { + ProxyAdmin proxyAdmin = ProxyAdmin(Env.proxyAdmin()); + ProxyAdmin beigenProxyAdmin = ProxyAdmin(Env.beigenProxyAdmin()); + + assertEq(proxyAdmin.owner(), Env.executorMultisig(), + "proxyAdmin.owner() != executorMultisig"); + require(address(Env.proxyAdmin()) != address(Env.beigenProxyAdmin()), + "tokens must have different proxy admins to allow different timelock controllers"); + require(address(Env.timelockController()) != address(Env.beigenTimelockController()), + "tokens must have different timelock controllers"); + + // note that proxy admin owners are different but _token_ owners per se are the same + assertEq(Ownable(address(Env.proxy.eigen())).owner(), address(Env.executorMultisig()), + "EIGEN.owner() != executorMultisig"); + assertEq(Ownable(address(Env.proxy.beigen())).owner(), address(Env.executorMultisig()), + "bEIGEN.owner() != executorMultisig"); + assertEq(proxyAdmin.owner(), address(Env.executorMultisig()), + "proxyAdmin.owner() != executorMultisig"); + assertEq(beigenProxyAdmin.owner(), address(Env.beigenExecutorMultisig()), + "beigenProxyAdmin.owner() != beigenExecutorMultisig"); + + assertEq(proxyAdmin.getProxyAdmin(ITransparentUpgradeableProxy(payable(address(Env.proxy.eigen())))), + Env.proxyAdmin(), + "proxyAdmin is not actually the admin of the EIGEN token"); + assertEq(beigenProxyAdmin.getProxyAdmin(ITransparentUpgradeableProxy(payable(address(Env.proxy.beigen())))), + Env.beigenProxyAdmin(), + "beigenProxyAdmin is not actually the admin of the bEIGEN token"); + + // check that community multisig and protocol timelock are the owners of the executorMultisig + checkExecutorMultisigOwnership(Env.executorMultisig(), address(Env.timelockController())); + // check that community multisig and bEIGEN protocol timelock are the owners of the beigenExecutorMultisig + checkExecutorMultisigOwnership(Env.beigenExecutorMultisig(), address(Env.beigenTimelockController())); + + checkTimelockControllerConfig(Env.timelockController()); + checkTimelockControllerConfig(Env.beigenTimelockController()); + + // TODO: this block commented-out because these contracts aren't deployed yet! move to another script? + // assertEq(delegationManager.owner(), Env.executorMultisig(), + // "delegationManager.owner() != executorMultisig"); + // assertEq(strategyManager.owner(), Env.executorMultisig(), + // "strategyManager.owner() != executorMultisig"); + // assertEq(strategyManager.strategyWhitelister(), address(strategyFactory), + // "strategyManager.strategyWhitelister() != address(strategyFactory)"); + // assertEq(strategyFactory.owner(), Env.opsMultisig(), + // "strategyFactory.owner() != operationsMultisig"); + // assertEq(avsDirectory.owner(), executorMultisig, + // "avsDirectory.owner() != executorMultisig"); + // assertEq(rewardsCoordinator.owner(), Env.opsMultisig(), + // "rewardsCoordinator.owner() != operationsMultisig"); + // assertEq(eigenLayerPauserReg.unpauser(), Env.executorMultisig(), + // "eigenLayerPauserReg.unpauser() != operationsMultisig"); + // require(eigenLayerPauserReg.isPauser(Env.opsMultisig()), + // "operationsMultisig does not have pausing permissions"); + // require(eigenLayerPauserReg.isPauser(Env.executorMultisig()), + // "executorMultisig does not have pausing permissions"); + // require(eigenLayerPauserReg.isPauser(pauserMultisig), + // "pauserMultisig does not have pausing permissions"); + // require(eigenPodBeacon.owner() == Env.executorMultisig(), "eigenPodBeacon: owner not set correctly"); + // require(strategyBeacon.owner() == Env.executorMultisig(), "strategyBeacon: owner not set correctly"); + } + + 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] == Env.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(), Env.protocolCouncilMultisig()), + "protocolCouncilMultisig does not have PROPOSER_ROLE on timelockController"); + require(timelockController.hasRole(timelockController.EXECUTOR_ROLE(), Env.protocolCouncilMultisig()), + "protocolCouncilMultisig does not have EXECUTOR_ROLE on timelockController"); + + // check for proposer + canceller rights on ops multisig + require(timelockController.hasRole(timelockController.PROPOSER_ROLE(), Env.opsMultisig()), + "operationsMultisig does not have PROPOSER_ROLE on timelockController"); + require(timelockController.hasRole(timelockController.CANCELLER_ROLE(), Env.opsMultisig()), + "operationsMultisig does not have CANCELLER_ROLE on timelockController"); + + // check that community multisig has admin rights + require(timelockController.hasRole(timelockController.TIMELOCK_ADMIN_ROLE(), Env.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"); + } +} diff --git a/script/releases/v0.0.1-gov/upgrade.json b/script/releases/v0.0.1-gov/upgrade.json new file mode 100644 index 0000000000..018874f2f6 --- /dev/null +++ b/script/releases/v0.0.1-gov/upgrade.json @@ -0,0 +1,11 @@ +{ + "name": "gov-init-protocol_council", + "from": "0.0.0", + "to": "0.0.1", + "phases": [ + { + "type": "eoa", + "filename": "1-eoa.s.sol" + } + ] +} \ No newline at end of file From 368c3c91f783088fe2f5f9b68b2876016e7152c7 Mon Sep 17 00:00:00 2001 From: MinisculeTarantula Date: Mon, 3 Mar 2025 16:49:14 -0800 Subject: [PATCH 2/9] fix: actually run check within test --- script/releases/v0.0.1-gov/1-eoa.s.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/script/releases/v0.0.1-gov/1-eoa.s.sol b/script/releases/v0.0.1-gov/1-eoa.s.sol index 02aa83799a..662b98b345 100644 --- a/script/releases/v0.0.1-gov/1-eoa.s.sol +++ b/script/releases/v0.0.1-gov/1-eoa.s.sol @@ -26,8 +26,7 @@ contract Deploy is EOADeployer { function testDeploy() public virtual { _runAsEOA(); - // TODO: seems more appropriate after more complete deployment! - // checkGovernanceConfiguration(); + checkGovernanceConfiguration(); } // set up initially with deployer as a proposer & executor, to be renounced prior to finalizing deployment From 3451dbd402927b3a407d873088293c3849d360de Mon Sep 17 00:00:00 2001 From: MinisculeTarantula Date: Tue, 4 Mar 2025 08:10:30 -0800 Subject: [PATCH 3/9] fix: delete duplicate 'startBroadcast' instruction --- script/releases/v0.0.1-gov/1-eoa.s.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/script/releases/v0.0.1-gov/1-eoa.s.sol b/script/releases/v0.0.1-gov/1-eoa.s.sol index 662b98b345..704a30ca46 100644 --- a/script/releases/v0.0.1-gov/1-eoa.s.sol +++ b/script/releases/v0.0.1-gov/1-eoa.s.sol @@ -37,7 +37,6 @@ contract Deploy is EOADeployer { address[] memory executors = new address[](1); executors[0] = msg.sender; - vm.startBroadcast(); deployImpl({ name: "timelockController", deployedTo: address(new TimelockController({ From 47b5bf69a0beed7d956b0d613b26563e926dba45 Mon Sep 17 00:00:00 2001 From: MinisculeTarantula Date: Tue, 4 Mar 2025 08:14:42 -0800 Subject: [PATCH 4/9] fix: use different salts in deployment this fixes an error `FAIL: revert: multisig deployment failed` from reusing salts --- script/releases/v0.0.1-gov/1-eoa.s.sol | 27 +++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/script/releases/v0.0.1-gov/1-eoa.s.sol b/script/releases/v0.0.1-gov/1-eoa.s.sol index 704a30ca46..d7a9c8f1d8 100644 --- a/script/releases/v0.0.1-gov/1-eoa.s.sol +++ b/script/releases/v0.0.1-gov/1-eoa.s.sol @@ -58,6 +58,11 @@ contract Deploy is EOADeployer { } function deployProtocolMultisigs() public { + + // TODO: consider frontunning of multisig deployment + // pseudorandom number + uint256 salt = 87883615229; + // deploy multisigs that simply have the deployer as their initial owner address[] memory singleOwner = new address[](1); singleOwner[0] = msg.sender; @@ -65,28 +70,32 @@ contract Deploy is EOADeployer { name: "pauserMultisig", deployedTo: deployMultisig({ initialOwners: singleOwner, - initialThreshold: 1 + initialThreshold: 1, + salt: ++salt }) }); deployImpl({ name: "opsMultisig", deployedTo: deployMultisig({ initialOwners: singleOwner, - initialThreshold: 1 + initialThreshold: 1, + salt: ++salt }) }); deployImpl({ name: "protocolCouncilMultisig", deployedTo: deployMultisig({ initialOwners: singleOwner, - initialThreshold: 1 + initialThreshold: 1, + salt: ++salt }) }); deployImpl({ name: "communityMultisig", deployedTo: deployMultisig({ initialOwners: singleOwner, - initialThreshold: 1 + initialThreshold: 1, + salt: ++salt }) }); @@ -100,7 +109,8 @@ contract Deploy is EOADeployer { name: "executorMultisig", deployedTo: deployMultisig({ initialOwners: owners_executorMultisig, - initialThreshold: 1 + initialThreshold: 1, + salt: ++salt }) }); @@ -114,12 +124,13 @@ contract Deploy is EOADeployer { name: "beigenExecutorMultisig", deployedTo: deployMultisig({ initialOwners: owners_beigenExecutorMultisig, - initialThreshold: 1 + initialThreshold: 1, + salt: ++salt }) }); } - function deployMultisig(address[] memory initialOwners, uint256 initialThreshold) public returns (address) { + function deployMultisig(address[] memory initialOwners, uint256 initialThreshold, uint256 salt) public returns (address) { // TODO: solution for local networks / those that do not have Safe deployed on them? // addresses taken from https://github.com/safe-global/safe-smart-account/blob/main/CHANGELOG.md#expected-addresses-with-deterministic-deployment-proxy-default address safeFactory = 0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2; @@ -127,8 +138,6 @@ contract Deploy is EOADeployer { address safeFallbackHandler = 0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4; bytes memory emptyData; - // TODO: is implementing a nonzero salt useful at all? if yes, this should be an input - uint256 salt = 0; bytes memory initializerData = abi.encodeWithSignature( "setup(address[],uint256,address,bytes,address,address,uint256,address)", From 4983ccb11d2202789c2d0d9e639d6f90e849f74f Mon Sep 17 00:00:00 2001 From: Justin Brower Date: Tue, 4 Mar 2025 11:46:59 -0500 Subject: [PATCH 5/9] Fix issues --- script/releases/Env.sol | 15 ++++ script/releases/v0.0.1-gov/1-eoa.s.sol | 120 ++++++++++++------------- 2 files changed, 73 insertions(+), 62 deletions(-) diff --git a/script/releases/Env.sol b/script/releases/Env.sol index 8fa6373d56..d8eb6f6dd2 100644 --- a/script/releases/Env.sol +++ b/script/releases/Env.sol @@ -49,6 +49,21 @@ library Env { DeployedImpl internal constant impl = DeployedImpl.A; DeployedInstance internal constant instance = DeployedInstance.A; + /** + * SAFE + */ + function safeFactory() internal view returns (address) { + return _envAddress("safeFactory"); + } + + function safeSingleton() internal view returns (address) { + return _envAddress("safeSingleton"); + } + + function safeFallbackHandler() internal view returns (address) { + return _envAddress("safeFallbackHandler"); + } + /** * env */ diff --git a/script/releases/v0.0.1-gov/1-eoa.s.sol b/script/releases/v0.0.1-gov/1-eoa.s.sol index d7a9c8f1d8..501bd21619 100644 --- a/script/releases/v0.0.1-gov/1-eoa.s.sol +++ b/script/releases/v0.0.1-gov/1-eoa.s.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.12; import {EOADeployer} from "zeus-templates/templates/EOADeployer.sol"; import "../Env.sol"; +import "zeus-templates/utils/ZEnvHelpers.sol"; import "@openzeppelin/contracts/governance/TimelockController.sol"; import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; @@ -11,6 +12,7 @@ import "src/test/mocks/EmptyContract.sol"; contract Deploy is EOADeployer { using Env for *; + using ZEnvHelpers for *; function _runAsEOA() internal override { vm.startBroadcast(); @@ -37,28 +39,28 @@ contract Deploy is EOADeployer { address[] memory executors = new address[](1); executors[0] = msg.sender; - deployImpl({ - name: "timelockController", - deployedTo: address(new TimelockController({ - minDelay: 0, // no delay for setup - proposers: proposers, - executors: executors, - admin: address(0) - })) + + TimelockController timelockController = new TimelockController({ + minDelay: 0, // no delay for setup + proposers: proposers, + executors: executors, + admin: address(0) }); - deployImpl({ - name: "beigenTimelockController", - deployedTo: address(new TimelockController({ - minDelay: 0, // no delay for setup - proposers: proposers, - executors: executors, - admin: address(0) - })) + + TimelockController beigenTimelockController = new TimelockController({ + minDelay: 0, // no delay for setup + proposers: proposers, + executors: executors, + admin: address(0) }); + + // eigen governance, in later zeus upgrades, is referred to via `const` addresses, and not as + // deployed contracts (e.g via `deployImpl`/`deployProxy`). As such, we use `zUpdate()` to update these values. + zUpdate('timelockController', address(timelockController)); + zUpdate('timelockController_BEIGEN', address(beigenTimelockController)); } function deployProtocolMultisigs() public { - // TODO: consider frontunning of multisig deployment // pseudorandom number uint256 salt = 87883615229; @@ -66,38 +68,34 @@ contract Deploy is EOADeployer { // deploy multisigs that simply have the deployer as their initial owner address[] memory singleOwner = new address[](1); singleOwner[0] = msg.sender; - deployImpl({ - name: "pauserMultisig", - deployedTo: deployMultisig({ - initialOwners: singleOwner, - initialThreshold: 1, - salt: ++salt - }) + + address pauserMultisig = deployMultisig({ + initialOwners: singleOwner, + initialThreshold: 1, + salt: ++salt }); - deployImpl({ - name: "opsMultisig", - deployedTo: deployMultisig({ - initialOwners: singleOwner, - initialThreshold: 1, - salt: ++salt - }) + zUpdate("pauserMultisig", pauserMultisig); + + address opsMultisig = deployMultisig({ + initialOwners: singleOwner, + initialThreshold: 1, + salt: ++salt }); - deployImpl({ - name: "protocolCouncilMultisig", - deployedTo: deployMultisig({ - initialOwners: singleOwner, - initialThreshold: 1, - salt: ++salt - }) + zUpdate("operationsMultisig", opsMultisig); + + address protocolCouncilMultisig = deployMultisig({ + initialOwners: singleOwner, + initialThreshold: 1, + salt: ++salt }); - deployImpl({ - name: "communityMultisig", - deployedTo: deployMultisig({ - initialOwners: singleOwner, - initialThreshold: 1, - salt: ++salt - }) + zUpdate("protocolCouncilMultisig", protocolCouncilMultisig); + + address communityMultisig = deployMultisig({ + initialOwners: singleOwner, + initialThreshold: 1, + salt: ++salt }); + zUpdate("communityMultisig", communityMultisig); // deploy primary executorMultisig require(address(Env.timelockController()) != address(0), @@ -105,14 +103,13 @@ contract Deploy is EOADeployer { address[] memory owners_executorMultisig = new address[](2); owners_executorMultisig[0] = address(Env.timelockController()); owners_executorMultisig[1] = Env.communityMultisig(); - deployImpl({ - name: "executorMultisig", - deployedTo: deployMultisig({ - initialOwners: owners_executorMultisig, - initialThreshold: 1, - salt: ++salt - }) + + address executorMultisig = deployMultisig({ + initialOwners: owners_executorMultisig, + initialThreshold: 1, + salt: ++salt }); + zUpdate("executorMultisig", executorMultisig); // deploy beigenExecutorMultisig require(address(Env.beigenTimelockController()) != address(0), @@ -120,22 +117,21 @@ contract Deploy is EOADeployer { address[] memory owners_beigenExecutorMultisig = new address[](2); owners_beigenExecutorMultisig[0] = address(Env.beigenTimelockController()); owners_beigenExecutorMultisig[1] = Env.communityMultisig(); - deployImpl({ - name: "beigenExecutorMultisig", - deployedTo: deployMultisig({ - initialOwners: owners_beigenExecutorMultisig, - initialThreshold: 1, - salt: ++salt - }) + + address beigenExecutorMultisig = deployMultisig({ + initialOwners: owners_beigenExecutorMultisig, + initialThreshold: 1, + salt: ++salt }); + zUpdate("beigenExecutorMultisig", beigenExecutorMultisig); } function deployMultisig(address[] memory initialOwners, uint256 initialThreshold, uint256 salt) public returns (address) { // TODO: solution for local networks / those that do not have Safe deployed on them? // addresses taken from https://github.com/safe-global/safe-smart-account/blob/main/CHANGELOG.md#expected-addresses-with-deterministic-deployment-proxy-default - address safeFactory = 0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2; - address safeSingleton = 0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552; - address safeFallbackHandler = 0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4; + address safeFactory = Env.safeFactory(); + address safeSingleton = Env.safeSingleton(); + address safeFallbackHandler = Env.safeFallbackHandler(); bytes memory emptyData; From da62f80abef21b78f0664aa57e8463de8c84e86f Mon Sep 17 00:00:00 2001 From: MinisculeTarantula Date: Tue, 4 Mar 2025 13:38:53 -0800 Subject: [PATCH 6/9] fix: correct eigen and beigen deployment fixes a few issues primarily related to confusion between the two tokens --- script/releases/v0.0.1-gov/1-eoa.s.sol | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/script/releases/v0.0.1-gov/1-eoa.s.sol b/script/releases/v0.0.1-gov/1-eoa.s.sol index 501bd21619..c72917cebe 100644 --- a/script/releases/v0.0.1-gov/1-eoa.s.sol +++ b/script/releases/v0.0.1-gov/1-eoa.s.sol @@ -57,7 +57,7 @@ contract Deploy is EOADeployer { // eigen governance, in later zeus upgrades, is referred to via `const` addresses, and not as // deployed contracts (e.g via `deployImpl`/`deployProxy`). As such, we use `zUpdate()` to update these values. zUpdate('timelockController', address(timelockController)); - zUpdate('timelockController_BEIGEN', address(beigenTimelockController)); + zUpdate('beigenTimelockController', address(beigenTimelockController)); } function deployProtocolMultisigs() public { @@ -230,16 +230,10 @@ contract Deploy is EOADeployer { } function deployTokensAndStrategy() public { - deployImpl({ - name: "proxyAdmin", - deployedTo: address(new ProxyAdmin()) - }); - deployImpl({ - name: "beigenProxyAdmin", - deployedTo: address(new ProxyAdmin()) - }); - ProxyAdmin proxyAdmin = ProxyAdmin(Env.proxyAdmin()); - ProxyAdmin beigenProxyAdmin = ProxyAdmin(Env.beigenProxyAdmin()); + ProxyAdmin proxyAdmin = new ProxyAdmin(); + zUpdate("proxyAdmin", address(proxyAdmin)); + ProxyAdmin beigenProxyAdmin = new ProxyAdmin(); + zUpdate("beigenProxyAdmin", address(beigenProxyAdmin)); // TODO: decide if this should be in env // placeholder used for initial proxy deployments since initial implementation must be a contract @@ -265,13 +259,13 @@ contract Deploy is EOADeployer { deployImpl({ name: type(BackingEigen).name, deployedTo: address(new BackingEigen({ - _EIGEN: IERC20(Env.proxy.eigen()) + _EIGEN: IERC20(Env.proxy.beigen()) })) }); deployImpl({ name: type(Eigen).name, deployedTo: address(new Eigen({ - _bEIGEN: IERC20(Env.proxy.beigen()) + _bEIGEN: IERC20(Env.proxy.eigen()) })) }); @@ -295,7 +289,7 @@ contract Deploy is EOADeployer { Eigen(address(Env.proxy.eigen())).transferOwnership(Env.executorMultisig()); // use deployer as initial owner, for editing minting permissions prior to transferring ownership - proxyAdmin.upgradeAndCall({ + beigenProxyAdmin.upgradeAndCall({ proxy: ITransparentUpgradeableProxy(address(Env.proxy.beigen())), implementation: address(Env.impl.beigen()), data: abi.encodeWithSelector( @@ -315,7 +309,7 @@ contract Deploy is EOADeployer { newAdmin: address(proxyAdmin) }); - proxyAdmin.changeProxyAdmin({ + beigenProxyAdmin.changeProxyAdmin({ proxy: ITransparentUpgradeableProxy(address(Env.proxy.beigen())), newAdmin: address(proxyAdmin) }); From 22923124f70eba30e11f4a6236edc70633020a15 Mon Sep 17 00:00:00 2001 From: MinisculeTarantula Date: Tue, 4 Mar 2025 13:46:14 -0800 Subject: [PATCH 7/9] fix: transfer proxy admin & token ownership appropriately --- script/releases/v0.0.1-gov/1-eoa.s.sol | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/script/releases/v0.0.1-gov/1-eoa.s.sol b/script/releases/v0.0.1-gov/1-eoa.s.sol index c72917cebe..06c1734cac 100644 --- a/script/releases/v0.0.1-gov/1-eoa.s.sol +++ b/script/releases/v0.0.1-gov/1-eoa.s.sol @@ -302,25 +302,17 @@ contract Deploy is EOADeployer { minterAddress: address(0), newStatus: true }); - BackingEigen(address(Env.proxy.beigen())).transferOwnership(Env.beigenExecutorMultisig()); + BackingEigen(address(Env.proxy.beigen())).transferOwnership(Env.executorMultisig()); - proxyAdmin.changeProxyAdmin({ - proxy: ITransparentUpgradeableProxy(address(Env.proxy.eigen())), - newAdmin: address(proxyAdmin) - }); - - beigenProxyAdmin.changeProxyAdmin({ - proxy: ITransparentUpgradeableProxy(address(Env.proxy.beigen())), - newAdmin: address(proxyAdmin) - }); + // transfer proxy admin ownership + proxyAdmin.transferOwnership(Env.executorMultisig()); + beigenProxyAdmin.transferOwnership(Env.beigenExecutorMultisig()); } function checkGovernanceConfiguration() public view { ProxyAdmin proxyAdmin = ProxyAdmin(Env.proxyAdmin()); ProxyAdmin beigenProxyAdmin = ProxyAdmin(Env.beigenProxyAdmin()); - assertEq(proxyAdmin.owner(), Env.executorMultisig(), - "proxyAdmin.owner() != executorMultisig"); require(address(Env.proxyAdmin()) != address(Env.beigenProxyAdmin()), "tokens must have different proxy admins to allow different timelock controllers"); require(address(Env.timelockController()) != address(Env.beigenTimelockController()), From 76681967811cf505cf38fd194daac826563a9b6a Mon Sep 17 00:00:00 2001 From: MinisculeTarantula Date: Tue, 4 Mar 2025 13:54:18 -0800 Subject: [PATCH 8/9] feat: perform initial token minting prior to transferring ownership this change should be especially practical for testnet environments --- script/releases/v0.0.1-gov/1-eoa.s.sol | 33 +++++++++++++++++--------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/script/releases/v0.0.1-gov/1-eoa.s.sol b/script/releases/v0.0.1-gov/1-eoa.s.sol index 06c1734cac..1095011e30 100644 --- a/script/releases/v0.0.1-gov/1-eoa.s.sol +++ b/script/releases/v0.0.1-gov/1-eoa.s.sol @@ -256,16 +256,19 @@ contract Deploy is EOADeployer { })) }); + address eigen = address(Env.proxy.eigen()); + address beigen = address(Env.proxy.beigen()); + deployImpl({ name: type(BackingEigen).name, deployedTo: address(new BackingEigen({ - _EIGEN: IERC20(Env.proxy.beigen()) + _EIGEN: IERC20(eigen) })) }); deployImpl({ name: type(Eigen).name, deployedTo: address(new Eigen({ - _bEIGEN: IERC20(Env.proxy.eigen()) + _bEIGEN: IERC20(beigen) })) }); @@ -275,7 +278,7 @@ contract Deploy is EOADeployer { uint256[] memory mintingAllowances; uint256[] memory mintAllowedAfters; proxyAdmin.upgradeAndCall({ - proxy: ITransparentUpgradeableProxy(address(Env.proxy.eigen())), + proxy: ITransparentUpgradeableProxy(eigen), implementation: address(Env.impl.eigen()), data: abi.encodeWithSelector( Eigen.initialize.selector, @@ -285,24 +288,32 @@ contract Deploy is EOADeployer { mintAllowedAfters ) }); - Eigen(address(Env.proxy.eigen())).disableTransferRestrictions(); - Eigen(address(Env.proxy.eigen())).transferOwnership(Env.executorMultisig()); + Eigen(eigen).disableTransferRestrictions(); + Eigen(eigen).transferOwnership(Env.executorMultisig()); - // use deployer as initial owner, for editing minting permissions prior to transferring ownership + // use deployer as initial owner, for minting first tokens prior to transferring ownership beigenProxyAdmin.upgradeAndCall({ - proxy: ITransparentUpgradeableProxy(address(Env.proxy.beigen())), + proxy: ITransparentUpgradeableProxy(beigen), implementation: address(Env.impl.beigen()), data: abi.encodeWithSelector( BackingEigen.initialize.selector, initialOwner ) }); - // TODO: get correct minterAddress here! - BackingEigen(address(Env.proxy.beigen())).setIsMinter({ - minterAddress: address(0), + // perform initial mint, renounce minting rights + transfer ownership + uint256 initialMintAmount = 1.6e27; + BackingEigen(beigen).setIsMinter({ + minterAddress: msg.sender, newStatus: true }); - BackingEigen(address(Env.proxy.beigen())).transferOwnership(Env.executorMultisig()); + BackingEigen(beigen).mint(msg.sender, initialMintAmount); + BackingEigen(beigen).setIsMinter({ + minterAddress: msg.sender, + newStatus: false + }); + BackingEigen(beigen).approve(eigen, initialMintAmount); + Eigen(eigen).wrap(initialMintAmount); + BackingEigen(beigen).transferOwnership(Env.executorMultisig()); // transfer proxy admin ownership proxyAdmin.transferOwnership(Env.executorMultisig()); From bed9bfe8cb8c30d3e77afadf6f3b3324bad57c10 Mon Sep 17 00:00:00 2001 From: MinisculeTarantula Date: Tue, 4 Mar 2025 13:55:06 -0800 Subject: [PATCH 9/9] chore: resolve remaining TODOs --- script/releases/v0.0.1-gov/1-eoa.s.sol | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/script/releases/v0.0.1-gov/1-eoa.s.sol b/script/releases/v0.0.1-gov/1-eoa.s.sol index 1095011e30..93f46d3c2e 100644 --- a/script/releases/v0.0.1-gov/1-eoa.s.sol +++ b/script/releases/v0.0.1-gov/1-eoa.s.sol @@ -61,7 +61,6 @@ contract Deploy is EOADeployer { } function deployProtocolMultisigs() public { - // TODO: consider frontunning of multisig deployment // pseudorandom number uint256 salt = 87883615229; @@ -127,7 +126,6 @@ contract Deploy is EOADeployer { } function deployMultisig(address[] memory initialOwners, uint256 initialThreshold, uint256 salt) public returns (address) { - // TODO: solution for local networks / those that do not have Safe deployed on them? // addresses taken from https://github.com/safe-global/safe-smart-account/blob/main/CHANGELOG.md#expected-addresses-with-deterministic-deployment-proxy-default address safeFactory = Env.safeFactory(); address safeSingleton = Env.safeSingleton(); @@ -353,30 +351,6 @@ contract Deploy is EOADeployer { checkTimelockControllerConfig(Env.timelockController()); checkTimelockControllerConfig(Env.beigenTimelockController()); - - // TODO: this block commented-out because these contracts aren't deployed yet! move to another script? - // assertEq(delegationManager.owner(), Env.executorMultisig(), - // "delegationManager.owner() != executorMultisig"); - // assertEq(strategyManager.owner(), Env.executorMultisig(), - // "strategyManager.owner() != executorMultisig"); - // assertEq(strategyManager.strategyWhitelister(), address(strategyFactory), - // "strategyManager.strategyWhitelister() != address(strategyFactory)"); - // assertEq(strategyFactory.owner(), Env.opsMultisig(), - // "strategyFactory.owner() != operationsMultisig"); - // assertEq(avsDirectory.owner(), executorMultisig, - // "avsDirectory.owner() != executorMultisig"); - // assertEq(rewardsCoordinator.owner(), Env.opsMultisig(), - // "rewardsCoordinator.owner() != operationsMultisig"); - // assertEq(eigenLayerPauserReg.unpauser(), Env.executorMultisig(), - // "eigenLayerPauserReg.unpauser() != operationsMultisig"); - // require(eigenLayerPauserReg.isPauser(Env.opsMultisig()), - // "operationsMultisig does not have pausing permissions"); - // require(eigenLayerPauserReg.isPauser(Env.executorMultisig()), - // "executorMultisig does not have pausing permissions"); - // require(eigenLayerPauserReg.isPauser(pauserMultisig), - // "pauserMultisig does not have pausing permissions"); - // require(eigenPodBeacon.owner() == Env.executorMultisig(), "eigenPodBeacon: owner not set correctly"); - // require(strategyBeacon.owner() == Env.executorMultisig(), "strategyBeacon: owner not set correctly"); } function checkExecutorMultisigOwnership(address _executorMultisig, address timelockControllerAddress) public view {