From 784a8366b1e712749cb77b709af0166d97a11f5a Mon Sep 17 00:00:00 2001 From: Bayological <6872903+bayological@users.noreply.github.com> Date: Mon, 18 Nov 2024 15:23:01 +0700 Subject: [PATCH] feat: add script to deploy locking proxy admin (#243) --- remappings.txt | 4 +- script/interfaces/IGovernanceFactory.sol | 2 + script/upgrades/MU09/MU09.md | 29 ++++++ script/upgrades/MU09/MU09.sol | 99 +++++++++++++++++++ script/upgrades/MU09/MU09Checks.sol | 90 +++++++++++++++++ .../deploy/MU09-Deploy-LockingProxyAdmin.sol | 32 ++++++ 6 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 script/upgrades/MU09/MU09.md create mode 100644 script/upgrades/MU09/MU09.sol create mode 100644 script/upgrades/MU09/MU09Checks.sol create mode 100644 script/upgrades/MU09/deploy/MU09-Deploy-LockingProxyAdmin.sol diff --git a/remappings.txt b/remappings.txt index c3fd2440..e7a74626 100644 --- a/remappings.txt +++ b/remappings.txt @@ -9,4 +9,6 @@ mento-core-2.1.0/=lib/mento-core-2.1.0/contracts/ mento-core-2.2.0/=lib/mento-core-2.2.0/contracts/ mento-core-2.3.1/=lib/mento-core-2.3.1/contracts/ mento-core-2.5.0/=lib/mento-core-2.5.0/contracts/ -merkle-distributor/=lib/merkle-distributor/contracts/ \ No newline at end of file +merkle-distributor/=lib/merkle-distributor/contracts/ +mento-core-2.6.0-oz/=lib/mento-core-2.6.0/lib/openzeppelin-contracts-next +mento-core-2.6.0/=lib/mento-core-2.6.0/contracts/ diff --git a/script/interfaces/IGovernanceFactory.sol b/script/interfaces/IGovernanceFactory.sol index 523191e9..436182f9 100644 --- a/script/interfaces/IGovernanceFactory.sol +++ b/script/interfaces/IGovernanceFactory.sol @@ -29,4 +29,6 @@ interface IGovernanceFactory { function mentoGovernor() external view returns (address); function locking() external view returns (address); + + function proxyAdmin() external view returns (address); } diff --git a/script/upgrades/MU09/MU09.md b/script/upgrades/MU09/MU09.md new file mode 100644 index 00000000..b88231a0 --- /dev/null +++ b/script/upgrades/MU09/MU09.md @@ -0,0 +1,29 @@ +### MU09: Transfer Locking Contract Ownership + +#### Description + +Currently, Mento Governance and Locking contracts have a circular dependency - any issues in the Locking contracts could prevent governance from executing upgrades since governance requires Locking to function properly. With the upcoming Celo L2 transition bringing breaking changes to Locking contracts, we need to temporarily transfer upgradability rights to another account to ensure we can upgrade Locking contracts without depending on them being in a perfect state. + +#### Changes + +1. Deploy a new ProxyAdmin contract owned by the MentoLabs multisig +2. Transfer proxy ownership of all Locking contracts to this new ProxyAdmin + +#### Motivation + +This change breaks the circular dependency between governance and Locking contracts, allowing us to: + +- Safely upgrade Locking contracts if issues arise +- Prepare for Celo L2 transition breaking changes +- Maintain ability to fix critical issues without being blocked by Locking contract state + +#### Security Considerations + +- The MentoLabs multisig will temporarily have direct upgrade rights over Locking contracts +- This is a temporary security tradeoff to prevent potential system deadlock +- Plan to return ownership to governance after L2 transition is complete + +#### Testing + +- Deploy ProxyAdmin with correct ownership(Mento labs multisig) +- Verify successful transfer of Locking proxy ownership diff --git a/script/upgrades/MU09/MU09.sol b/script/upgrades/MU09/MU09.sol new file mode 100644 index 00000000..ad482c0f --- /dev/null +++ b/script/upgrades/MU09/MU09.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// solhint-disable func-name-mixedcase, contract-name-camelcase, function-max-lines, var-name-mixedcase +pragma solidity ^0.8; +pragma experimental ABIEncoderV2; + +import { GovernanceScript } from "script/utils/mento/Script.sol"; +import { console } from "forge-std/console.sol"; +import { Contracts } from "script/utils/mento/Contracts.sol"; +import { Chain } from "script/utils/mento/Chain.sol"; + +import { IGovernanceFactory } from "script/interfaces/IGovernanceFactory.sol"; +import { IMentoUpgrade, ICeloGovernance } from "script/interfaces/IMentoUpgrade.sol"; + +interface IProxyAdminLite { + function getProxyAdmin(address proxy) external view returns (address); + + function changeProxyAdmin(address proxy, address newAdmin) external; +} + +contract MU09 is IMentoUpgrade, GovernanceScript { + using Contracts for Contracts.Cache; + + bool public hasChecks = true; + + address public mentoGovernor; + address public lockingProxyAdmin; + address public lockingProxy; + + address public oldLockingProxyAdmin; + + IGovernanceFactory public governanceFactory; + + /** + * @dev Loads the contracts from previous deployments + */ + function loadDeployedContracts() public { + // Load load deployment with governance factory + contracts.loadSilent("MUGOV-00-Create-Factory", "latest"); + + // Load the deployed ProxyAdmin contract + contracts.loadSilent("MU09-Deploy-LockingProxyAdmin", "latest"); + } + + function prepare() public { + loadDeployedContracts(); + + // Get newly deployed LockingProxyAdmin address + lockingProxyAdmin = contracts.deployed("ProxyAdmin"); + + // Get and set the governance factory + address governanceFactoryAddress = contracts.deployed("GovernanceFactory"); + governanceFactory = IGovernanceFactory(governanceFactoryAddress); + + // Get the mento governor address + mentoGovernor = governanceFactory.mentoGovernor(); + require(mentoGovernor != address(0), "MentoGovernor address not found"); + + // Get the locking proxy address + lockingProxy = governanceFactory.locking(); + require(lockingProxy != address(0), "LockingProxy address not found"); + + // Get the old locking proxy admin address + oldLockingProxyAdmin = governanceFactory.proxyAdmin(); + require(oldLockingProxyAdmin != address(0), "Old LockingProxyAdmin address not found"); + } + + function run() public { + prepare(); + + ICeloGovernance.Transaction[] memory _transactions = buildProposal(); + + vm.startBroadcast(Chain.deployerPrivateKey()); + { + // TODO: Change this to the forum post URL + createProposal(_transactions, "https://CHANGE-ME-PLEASE", mentoGovernor); + } + vm.stopBroadcast(); + } + + function buildProposal() public returns (ICeloGovernance.Transaction[] memory) { + ICeloGovernance.Transaction[] memory _transactions = new ICeloGovernance.Transaction[](1); + + // Check that the proxy admin of locking is the proxy admin from the governance factory + address proxyAdminOfLocking = IProxyAdminLite(oldLockingProxyAdmin).getProxyAdmin(lockingProxy); + require( + proxyAdminOfLocking == oldLockingProxyAdmin, + "Proxy admin of locking is not `governanceFactory.proxyAdmin()`" + ); + + // Send tx to the old proxy admin to change the proxy admin of locking to the new locking proxy admin + _transactions[0] = ICeloGovernance.Transaction( + 0, + oldLockingProxyAdmin, + abi.encodeWithSelector(IProxyAdminLite.changeProxyAdmin.selector, lockingProxy, lockingProxyAdmin) + ); + + return _transactions; + } +} diff --git a/script/upgrades/MU09/MU09Checks.sol b/script/upgrades/MU09/MU09Checks.sol new file mode 100644 index 00000000..b98c5d5d --- /dev/null +++ b/script/upgrades/MU09/MU09Checks.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +pragma solidity ^0.8; +pragma experimental ABIEncoderV2; +/* solhint-disable max-line-length */ + +import { GovernanceScript } from "script/utils/mento/Script.sol"; +import { console } from "forge-std/console.sol"; +import { Contracts } from "script/utils/mento/Contracts.sol"; +import { Test } from "forge-std/Test.sol"; + +import { IGovernanceFactory } from "script/interfaces/IGovernanceFactory.sol"; +import { ITransparentUpgradeableProxy } from "mento-core-2.6.0/../lib/openzeppelin-contracts-next/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { ProxyAdmin } from "mento-core-2.6.0/../lib/openzeppelin-contracts-next/contracts/proxy/transparent/ProxyAdmin.sol"; + +contract MockImplementation {} + +contract MU09Checks is GovernanceScript, Test { + using Contracts for Contracts.Cache; + + address public newLockingProxyAdmin; + address public lockingProxy; + address public mentoLabsMultisig; + + event Upgraded(address indexed newImplementation); + + function prepare() public { + // Load addresses from deployments + contracts.loadSilent("MUGOV-00-Create-Factory", "latest"); + contracts.loadSilent("MU09-Deploy-LockingProxyAdmin", "latest"); + + mentoLabsMultisig = contracts.dependency("MentoLabsMultisig"); + require(mentoLabsMultisig != address(0), "MentoLabsMultisig address not found"); + + // Get newly deployed LockingProxyAdmin address + newLockingProxyAdmin = contracts.deployed("ProxyAdmin"); + require(newLockingProxyAdmin != address(0), "LockingProxyAdmin address not found"); + + // Get and set the governance factory + address governanceFactoryAddress = contracts.deployed("GovernanceFactory"); + require(governanceFactoryAddress != address(0), "GovernanceFactory address not found"); + IGovernanceFactory governanceFactory = IGovernanceFactory(governanceFactoryAddress); + + // Get the locking proxy address + lockingProxy = governanceFactory.locking(); + require(lockingProxy != address(0), "LockingProxy address not found"); + } + + function run() public { + console.log("\nStarting MU09 checks:"); + prepare(); + + verifyLockingProxyAdminOwnership(); + verifyLockingProxyAdminIsNewLockingProxyAdmin(); + verifyMultisigCanUpgrade(); + } + + function verifyLockingProxyAdminOwnership() public { + console.log("\n== Verifying locking proxy admin ownership: =="); + + address lockingProxyAdminOwner = ProxyAdmin(newLockingProxyAdmin).owner(); + require(lockingProxyAdminOwner == mentoLabsMultisig, "LockingProxyAdmin owner is not MentoLabsMultisig"); + console.log(unicode"🟢 LockingProxyAdmin owner is MentoLabsMultisig: %s", lockingProxyAdminOwner); + } + + function verifyLockingProxyAdminIsNewLockingProxyAdmin() public { + console.log("\n== Verifying locking proxy admin is new locking proxy admin: =="); + + address lockingProxyAdmin = ProxyAdmin(newLockingProxyAdmin).getProxyAdmin( + ITransparentUpgradeableProxy(lockingProxy) + ); + require(lockingProxyAdmin == newLockingProxyAdmin, "LockingProxyAdmin is not the new LockingProxyAdmin"); + console.log(unicode"🟢 LockingProxyAdmin is new LockingProxyAdmin: %s", lockingProxyAdmin); + } + + function verifyMultisigCanUpgrade() public { + console.log("\n== Verifying the multisig can successfuly upgrade implementation: =="); + + // Deploy a mock implementation + MockImplementation mockImplementation = new MockImplementation(); + vm.startPrank(mentoLabsMultisig); + + // Verify that the upgrade event is emitted with the fake implementation + vm.expectEmit(true, true, true, true); + emit Upgraded(address(mockImplementation)); + + ProxyAdmin(newLockingProxyAdmin).upgrade(ITransparentUpgradeableProxy(lockingProxy), address(mockImplementation)); + + console.log(unicode"🟢 Multisig can upgrade implementation"); + } +} diff --git a/script/upgrades/MU09/deploy/MU09-Deploy-LockingProxyAdmin.sol b/script/upgrades/MU09/deploy/MU09-Deploy-LockingProxyAdmin.sol new file mode 100644 index 00000000..b6d7b092 --- /dev/null +++ b/script/upgrades/MU09/deploy/MU09-Deploy-LockingProxyAdmin.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +// solhint-disable contract-name-camelcase +pragma solidity ^0.8.18; + +import { console } from "forge-std-next/console.sol"; +import { Script } from "script/utils/mento/Script.sol"; +import { Chain as ChainLib } from "script/utils/mento/Chain.sol"; +import { Contracts } from "script/utils/mento/Contracts.sol"; +import { ProxyAdmin } from "mento-core-2.6.0-oz/contracts/proxy/transparent/ProxyAdmin.sol"; + +interface IOwnableLite { + function transferOwnership(address newOwner) external; +} + +contract MU09_Deploy_LockingProxyAdmin is Script { + using Contracts for Contracts.Cache; + + function run() public { + address mentoLabsMultisig = contracts.dependency("MentoLabsMultisig"); + require(mentoLabsMultisig != address(0), "MentoLabsMultisig address not found"); + + vm.startBroadcast(ChainLib.deployerPrivateKey()); + { + IOwnableLite proxyAdmin = IOwnableLite(address(new ProxyAdmin())); + console.log("Deployed ProxyAdmin for Locking at: %s", address(proxyAdmin)); + + proxyAdmin.transferOwnership(mentoLabsMultisig); + console.log("Transferred LockingProxyAdmin ownership to MentoLabsMultisig: %s", mentoLabsMultisig); + } + vm.stopBroadcast(); + } +}