diff --git a/script/releases/v0.5.2-rewardsv2/1-eoa.s.sol b/script/releases/v0.5.2-rewardsv2/1-eoa.s.sol new file mode 100644 index 0000000000..239dbb7348 --- /dev/null +++ b/script/releases/v0.5.2-rewardsv2/1-eoa.s.sol @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.12; + +import {EOADeployer} from "zeus-templates/templates/EOADeployer.sol"; +import {RewardsCoordinator} from "src/contracts/core/RewardsCoordinator.sol"; +import {IDelegationManager} from "src/contracts/interfaces/IDelegationManager.sol"; +import {DelegationManager} from "src/contracts/core/DelegationManager.sol"; +import {StrategyManager} from "src/contracts/core/StrategyManager.sol"; +import {EigenLabsUpgrade} from "../EigenLabsUpgrade.s.sol"; +import {Test, console} from "forge-std/Test.sol"; +import {IPauserRegistry} from "src/contracts/interfaces/IPauserRegistry.sol"; + +contract Deploy is EOADeployer { + using EigenLabsUpgrade for *; + + function _runAsEOA() internal override { + zUpdateUint16(string("REWARDS_COORDINATOR_DEFAULT_OPERATOR_SPLIT_BIPS"), uint16(1000)); + zUpdateUint32(string("REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS"), uint32(1 days)); + + vm.startBroadcast(); + deploySingleton( + address( + new RewardsCoordinator( + IDelegationManager(zDeployedProxy(type(DelegationManager).name)), + StrategyManager(zDeployedProxy(type(StrategyManager).name)), + zUint32("REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS"), + zUint32("REWARDS_COORDINATOR_MAX_REWARDS_DURATION"), + zUint32("REWARDS_COORDINATOR_MAX_RETROACTIVE_LENGTH"), + zUint32("REWARDS_COORDINATOR_MAX_FUTURE_LENGTH"), + zUint32("REWARDS_COORDINATOR_GENESIS_REWARDS_TIMESTAMP") + ) + ), + this.impl(type(RewardsCoordinator).name) + ); + + vm.stopBroadcast(); + } + + function testDeploy() public virtual { + // Deploy RewardsCoordinator Implementation + address oldImpl = zDeployedImpl(type(RewardsCoordinator).name); + runAsEOA(); + address newImpl = zDeployedImpl(type(RewardsCoordinator).name); + assertTrue(oldImpl != newImpl, "impl should be different"); + + Deployment[] memory deploys = deploys(); + + // sanity check that zDeployedImpl is returning our deployment. + assertEq(deploys[0].deployedTo, zDeployedImpl(type(RewardsCoordinator).name)); + + RewardsCoordinator rewardsCoordinatorImpl = RewardsCoordinator(zDeployedImpl(type(RewardsCoordinator).name)); + + address owner = this._operationsMultisig(); + IPauserRegistry pauserRegistry = IPauserRegistry(this._pauserRegistry()); + uint64 initPausedStatus = zUint64("REWARDS_COORDINATOR_INIT_PAUSED_STATUS"); + address rewardsUpdater = zAddress("REWARDS_COORDINATOR_UPDATER"); + uint32 activationDelay = zUint32("REWARDS_COORDINATOR_ACTIVATION_DELAY"); + uint16 defaultOperatorSplitBips = zUint16("REWARDS_COORDINATOR_DEFAULT_OPERATOR_SPLIT_BIPS"); + + // Ensure that the implementation contract cannot be initialized. + vm.expectRevert("Initializable: contract is already initialized"); + rewardsCoordinatorImpl.initialize( + owner, + pauserRegistry, + initPausedStatus, + rewardsUpdater, + activationDelay, + defaultOperatorSplitBips + ); + + // Assert Immutables and State Variables set through initialize + assertEq(rewardsCoordinatorImpl.owner(), address(0), "expected owner"); + assertEq(address(rewardsCoordinatorImpl.pauserRegistry()), address(0), "expected pauserRegistry"); + assertEq(address(rewardsCoordinatorImpl.rewardsUpdater()), address(0), "expected rewardsUpdater"); + assertEq(rewardsCoordinatorImpl.activationDelay(), 0, "expected activationDelay"); + assertEq(rewardsCoordinatorImpl.defaultOperatorSplitBips(), 0, "expected defaultOperatorSplitBips"); + + assertEq( + address(rewardsCoordinatorImpl.delegationManager()), + zDeployedProxy(type(DelegationManager).name), + "expected delegationManager" + ); + assertEq( + address(rewardsCoordinatorImpl.strategyManager()), + zDeployedProxy(type(StrategyManager).name), + "expected strategyManager" + ); + + assertEq( + rewardsCoordinatorImpl.CALCULATION_INTERVAL_SECONDS(), + zUint32("REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS"), + "expected REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS" + ); + assertEq( + rewardsCoordinatorImpl.CALCULATION_INTERVAL_SECONDS(), + 1 days, + "expected REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS" + ); + assertGt( + rewardsCoordinatorImpl.CALCULATION_INTERVAL_SECONDS(), + 0, + "expected non-zero REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS" + ); + + assertEq(rewardsCoordinatorImpl.MAX_REWARDS_DURATION(), zUint32("REWARDS_COORDINATOR_MAX_REWARDS_DURATION")); + assertGt(rewardsCoordinatorImpl.MAX_REWARDS_DURATION(), 0); + + assertEq( + rewardsCoordinatorImpl.MAX_RETROACTIVE_LENGTH(), + zUint32("REWARDS_COORDINATOR_MAX_RETROACTIVE_LENGTH") + ); + assertGt(rewardsCoordinatorImpl.MAX_RETROACTIVE_LENGTH(), 0); + + assertEq(rewardsCoordinatorImpl.MAX_FUTURE_LENGTH(), zUint32("REWARDS_COORDINATOR_MAX_FUTURE_LENGTH")); + assertGt(rewardsCoordinatorImpl.MAX_FUTURE_LENGTH(), 0); + + assertEq( + rewardsCoordinatorImpl.GENESIS_REWARDS_TIMESTAMP(), + zUint32("REWARDS_COORDINATOR_GENESIS_REWARDS_TIMESTAMP") + ); + assertGt(rewardsCoordinatorImpl.GENESIS_REWARDS_TIMESTAMP(), 0); + + assertEq(deploys.length, 1, "expected exactly 1 deployment"); + assertEq( + keccak256(bytes(deploys[0].name)), + keccak256(bytes(this.impl(type(RewardsCoordinator).name))), + "zeusTest: Deployment name is not RewardsCoordinator" + ); + assertTrue(deploys[0].singleton == true, "zeusTest: RewardsCoordinator should be a singleton."); + assertNotEq(deploys[0].deployedTo, address(0), "zeusTest: Should deploy to non-zero address."); + } +} diff --git a/script/releases/v0.5.2-rewardsv2/2-multisig.s.sol b/script/releases/v0.5.2-rewardsv2/2-multisig.s.sol new file mode 100644 index 0000000000..c43b2a5b37 --- /dev/null +++ b/script/releases/v0.5.2-rewardsv2/2-multisig.s.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.12; + +import {MultisigCall, MultisigCallUtils, MultisigBuilder} from "zeus-templates/templates/MultisigBuilder.sol"; +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {Deploy} from "./1-eoa.s.sol"; +import {RewardsCoordinator} from "src/contracts/core/RewardsCoordinator.sol"; +import {EigenLabsUpgrade} from "../EigenLabsUpgrade.s.sol"; +import {IPauserRegistry} from "src/contracts/interfaces/IPauserRegistry.sol"; +import {ITimelock} from "zeus-templates/interfaces/ITimelock.sol"; +import {console} from "forge-std/console.sol"; +import {EncGnosisSafe} from "zeus-templates/utils/EncGnosisSafe.sol"; +import {MultisigCallUtils, MultisigCall} from "zeus-templates/utils/MultisigCallUtils.sol"; +import {IMultiSend} from "zeus-templates/interfaces/IMultiSend.sol"; +import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; + +/** + * Purpose: enqueue a multisig transaction which tells the ProxyAdmin to upgrade RewardsCoordinator. + */ +contract Queue is MultisigBuilder, Deploy { + using MultisigCallUtils for MultisigCall[]; + using EigenLabsUpgrade for *; + using EncGnosisSafe for *; + using MultisigCallUtils for *; + + MultisigCall[] private _executorCalls; + MultisigCall[] private _opsCalls; + + function options() internal virtual override view returns (MultisigOptions memory) { + return MultisigOptions( + this._operationsMultisig(), + Operation.Call + ); + } + + function _getMultisigTransactionCalldata() internal view returns (bytes memory) { + ProxyAdmin pa = ProxyAdmin(this._proxyAdmin()); + + bytes memory proxyAdminCalldata = abi.encodeCall( + pa.upgrade, + ( + TransparentUpgradeableProxy(payable(zDeployedProxy(type(RewardsCoordinator).name))), + zDeployedImpl(type(RewardsCoordinator).name) + ) + ); + + bytes memory executorMultisigCalldata = address(this._timelock()).calldataToExecTransaction( + this._proxyAdmin(), + proxyAdminCalldata, + EncGnosisSafe.Operation.Call + ); + + return (executorMultisigCalldata); + } + + function runAsMultisig() internal virtual override { + bytes memory executorMultisigCalldata = _getMultisigTransactionCalldata(); + + TimelockController timelock = TimelockController(payable(this._timelock())); + timelock.schedule( + this._executorMultisig(), + 0 /* value */, + executorMultisigCalldata, + 0 /* predecessor */, + bytes32(0) /* salt */, + timelock.getMinDelay() + ); + } + + function testDeploy() public virtual override { + runAsEOA(); + + execute(); + TimelockController timelock = TimelockController(payable(this._timelock())); + + bytes memory multisigTxnData = _getMultisigTransactionCalldata(); + bytes32 txHash = timelock.hashOperation(this._executorMultisig(), 0, multisigTxnData, 0, 0); + + assertEq(timelock.isOperationPending(txHash), true, "Transaction should be queued."); + } +} diff --git a/script/releases/v0.5.2-rewardsv2/3-multisig.s.sol b/script/releases/v0.5.2-rewardsv2/3-multisig.s.sol new file mode 100644 index 0000000000..4b46fb34ef --- /dev/null +++ b/script/releases/v0.5.2-rewardsv2/3-multisig.s.sol @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.12; + +import {MultisigCall, MultisigCallUtils, MultisigBuilder} from "zeus-templates/templates/MultisigBuilder.sol"; +import {SafeTx, SafeTxUtils} from "zeus-templates/utils/SafeTxUtils.sol"; +import {Queue} from "./2-multisig.s.sol"; +import {EigenLabsUpgrade} from "../EigenLabsUpgrade.s.sol"; +import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol"; +import {RewardsCoordinator} from "src/contracts/core/RewardsCoordinator.sol"; +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import {IPauserRegistry} from "src/contracts/interfaces/IPauserRegistry.sol"; +import {DelegationManager} from "src/contracts/core/DelegationManager.sol"; +import {StrategyManager} from "src/contracts/core/StrategyManager.sol"; +import {console} from "forge-std/console.sol"; + +contract Execute is Queue { + using MultisigCallUtils for MultisigCall[]; + using SafeTxUtils for SafeTx; + using EigenLabsUpgrade for *; + + event Upgraded(address indexed implementation); + + function options() internal view override returns (MultisigOptions memory) { + return MultisigOptions(this._protocolCouncilMultisig(), Operation.Call); + } + + /** + * @dev Overrides the previous _execute function to execute the queued transactions. + */ + function runAsMultisig() internal override { + bytes memory executorMultisigCalldata = _getMultisigTransactionCalldata(); + TimelockController timelock = TimelockController(payable(this._timelock())); + timelock.execute( + this._executorMultisig(), + 0 /* value */, + executorMultisigCalldata, + 0 /* predecessor */, + bytes32(0) /* salt */ + ); + } + + function testDeploy() public override { + // save the previous implementation address to assert its change later + address prevRewardsCoordinator = zDeployedImpl(type(RewardsCoordinator).name); + + // 0. Deploy the Implementation contract. + runAsEOA(); + + // 1. run the queue script. + vm.startPrank(this._operationsMultisig()); + super.runAsMultisig(); + vm.stopPrank(); + + RewardsCoordinator rewardsCoordinatorProxy = RewardsCoordinator(zDeployedProxy(type(RewardsCoordinator).name)); + uint256 pausedStatusBefore = rewardsCoordinatorProxy.paused(); + TimelockController timelock = this._timelock(); + + // 2. run the execute script above. + bytes memory multisigTxnData = _getMultisigTransactionCalldata(); + bytes32 txHash = timelock.hashOperation(this._executorMultisig(), 0, multisigTxnData, 0, 0); + + assertEq(timelock.isOperationPending(txHash), true, "Transaction should be queued and pending."); + vm.warp(block.timestamp + timelock.getMinDelay()); // 1 tick after ETA. + + assertEq(timelock.isOperationReady(txHash), true, "Transaction should be executable."); + + vm.expectEmit(true, true, true, true, address(rewardsCoordinatorProxy)); + emit Upgraded(zDeployedImpl(type(RewardsCoordinator).name)); + execute(); + + // 3. assert that the execute did something + assertEq(timelock.isOperationDone(txHash), true, "Transaction should be executed."); + + // assert that the proxy implementation was updated. + ProxyAdmin admin = ProxyAdmin(this._proxyAdmin()); + address rewardsCoordinatorImpl = admin.getProxyImplementation( + TransparentUpgradeableProxy(payable(zDeployedProxy(type(RewardsCoordinator).name))) + ); + assertEq(rewardsCoordinatorImpl, zDeployedImpl(type(RewardsCoordinator).name)); + assertNotEq(prevRewardsCoordinator, rewardsCoordinatorImpl, "expected rewardsCoordinatorImpl to be different"); + + uint256 pausedStatusAfter = rewardsCoordinatorProxy.paused(); + address owner = this._operationsMultisig(); + IPauserRegistry pauserRegistry = IPauserRegistry(this._pauserRegistry()); + uint64 initPausedStatus = zUint64("REWARDS_COORDINATOR_INIT_PAUSED_STATUS"); + address rewardsUpdater = zAddress("REWARDS_COORDINATOR_UPDATER"); + uint32 activationDelay = zUint32("REWARDS_COORDINATOR_ACTIVATION_DELAY"); + uint16 defaultOperatorSplitBips = zUint16("REWARDS_COORDINATOR_DEFAULT_OPERATOR_SPLIT_BIPS"); + + // Ensure that the proxy contract cannot be re-initialized. + vm.expectRevert("Initializable: contract is already initialized"); + rewardsCoordinatorProxy.initialize( + owner, + pauserRegistry, + initPausedStatus, + rewardsUpdater, + activationDelay, + defaultOperatorSplitBips + ); + + // Assert Immutables and State Variables set through initialize + assertEq(rewardsCoordinatorProxy.owner(), owner, "expected owner"); + assertEq(address(rewardsCoordinatorProxy.pauserRegistry()), address(pauserRegistry), "expected pauserRegistry"); + assertEq(address(rewardsCoordinatorProxy.rewardsUpdater()), rewardsUpdater, "expected rewardsUpdater"); + assertEq(rewardsCoordinatorProxy.activationDelay(), activationDelay, "expected activationDelay"); + assertEq( + rewardsCoordinatorProxy.defaultOperatorSplitBips(), + defaultOperatorSplitBips, + "expected defaultOperatorSplitBips" + ); + assertEq( + pausedStatusBefore, + pausedStatusAfter, + "expected paused status to be the same before and after initialization" + ); + assertEq( + address(rewardsCoordinatorProxy.delegationManager()), + zDeployedProxy(type(DelegationManager).name), + "expected delegationManager" + ); + assertEq( + address(rewardsCoordinatorProxy.strategyManager()), + zDeployedProxy(type(StrategyManager).name), + "expected strategyManager" + ); + + assertEq( + rewardsCoordinatorProxy.CALCULATION_INTERVAL_SECONDS(), + zUint32("REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS"), + "expected REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS" + ); + assertEq( + rewardsCoordinatorProxy.CALCULATION_INTERVAL_SECONDS(), + 1 days, + "expected REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS" + ); + assertGt( + rewardsCoordinatorProxy.CALCULATION_INTERVAL_SECONDS(), + 0, + "expected non-zero REWARDS_COORDINATOR_CALCULATION_INTERVAL_SECONDS" + ); + + assertEq(rewardsCoordinatorProxy.MAX_REWARDS_DURATION(), zUint32("REWARDS_COORDINATOR_MAX_REWARDS_DURATION")); + assertGt(rewardsCoordinatorProxy.MAX_REWARDS_DURATION(), 0); + + assertEq( + rewardsCoordinatorProxy.MAX_RETROACTIVE_LENGTH(), + zUint32("REWARDS_COORDINATOR_MAX_RETROACTIVE_LENGTH") + ); + assertGt(rewardsCoordinatorProxy.MAX_RETROACTIVE_LENGTH(), 0); + + assertEq(rewardsCoordinatorProxy.MAX_FUTURE_LENGTH(), zUint32("REWARDS_COORDINATOR_MAX_FUTURE_LENGTH")); + assertGt(rewardsCoordinatorProxy.MAX_FUTURE_LENGTH(), 0); + + assertEq( + rewardsCoordinatorProxy.GENESIS_REWARDS_TIMESTAMP(), + zUint32("REWARDS_COORDINATOR_GENESIS_REWARDS_TIMESTAMP") + ); + assertGt(rewardsCoordinatorProxy.GENESIS_REWARDS_TIMESTAMP(), 0); + } +} diff --git a/script/releases/v0.5.2-rewardsv2/upgrade.json b/script/releases/v0.5.2-rewardsv2/upgrade.json new file mode 100644 index 0000000000..e57322c851 --- /dev/null +++ b/script/releases/v0.5.2-rewardsv2/upgrade.json @@ -0,0 +1,19 @@ +{ + "name": "rewards-v2-patch", + "from": "~0.0.0", + "to": "0.5.2", + "phases": [ + { + "type": "eoa", + "filename": "1-eoa.s.sol" + }, + { + "type": "multisig", + "filename": "2-multisig.s.sol" + }, + { + "type": "multisig", + "filename": "3-multisig.s.sol" + } + ] +}