Skip to content

Commit

Permalink
feat: add script to deploy locking proxy admin (#243)
Browse files Browse the repository at this point in the history
  • Loading branch information
bayological authored Nov 18, 2024
1 parent 0493a31 commit 784a836
Show file tree
Hide file tree
Showing 6 changed files with 255 additions and 1 deletion.
4 changes: 3 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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/
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/
2 changes: 2 additions & 0 deletions script/interfaces/IGovernanceFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@ interface IGovernanceFactory {
function mentoGovernor() external view returns (address);

function locking() external view returns (address);

function proxyAdmin() external view returns (address);
}
29 changes: 29 additions & 0 deletions script/upgrades/MU09/MU09.md
Original file line number Diff line number Diff line change
@@ -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
99 changes: 99 additions & 0 deletions script/upgrades/MU09/MU09.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
90 changes: 90 additions & 0 deletions script/upgrades/MU09/MU09Checks.sol
Original file line number Diff line number Diff line change
@@ -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");
}
}
32 changes: 32 additions & 0 deletions script/upgrades/MU09/deploy/MU09-Deploy-LockingProxyAdmin.sol
Original file line number Diff line number Diff line change
@@ -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();
}
}

0 comments on commit 784a836

Please sign in to comment.