diff --git a/contracts/core/accounts/lib/LibAccounts.sol b/contracts/core/accounts/lib/LibAccounts.sol index 9d347514f..2c81d5133 100644 --- a/contracts/core/accounts/lib/LibAccounts.sol +++ b/contracts/core/accounts/lib/LibAccounts.sol @@ -1,7 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import {IterableMappingAddr} from "../../../lib/IterableMappingAddr.sol"; import {AccountStorage} from "../storage.sol"; library LibAccounts { diff --git a/contracts/lib/IterableMappingAddr.sol b/contracts/lib/IterableMappingAddr.sol index 53aea8644..3f5fb5677 100644 --- a/contracts/lib/IterableMappingAddr.sol +++ b/contracts/lib/IterableMappingAddr.sol @@ -2,9 +2,6 @@ pragma solidity ^0.8.19; contract IterableMappingAddr { - error NonExistentKey(address key); - error DecrAmountExceedsValue(address key, uint256 currVal, uint256 decrVal); - struct Map { address[] keys; mapping(address => bool) values; diff --git a/contracts/multisigs/endowment-multisig/EndowmentMultiSigFactory.sol b/contracts/multisigs/endowment-multisig/EndowmentMultiSigFactory.sol index 00ecf0ad9..1249eadf0 100644 --- a/contracts/multisigs/endowment-multisig/EndowmentMultiSigFactory.sol +++ b/contracts/multisigs/endowment-multisig/EndowmentMultiSigFactory.sol @@ -7,55 +7,58 @@ import {IEndowmentMultiSigFactory} from "./interfaces/IEndowmentMultiSigFactory. import {ProxyContract} from "../../core/proxy.sol"; import {IRegistrar} from "../../core/registrar/interfaces/IRegistrar.sol"; import {RegistrarStorage} from "../../core/registrar/storage.sol"; +import {Validator} from "../../core/validator.sol"; +import {IterableMappingAddr} from "../../lib/IterableMappingAddr.sol"; /// @title Multisignature wallet factory - Allows creation of multisigs wallet. -/// @author Stefan George - -contract EndowmentMultiSigFactory is IEndowmentMultiSigFactory, Ownable { - /* - * Events - */ - event ContractInstantiated(address sender, address instantiation); - event ImplementationUpdated(address implementationAddress); - event ProxyAdminUpdated(address admin); - - /* - * Storage - */ - mapping(address => bool) public isInstantiation; - mapping(address => address[]) public instantiations; - mapping(uint256 => address) public endowmentIdToMultisig; +contract EndowmentMultiSigFactory is IEndowmentMultiSigFactory, Ownable, IterableMappingAddr { + /*//////////////////////////////////////////////// + STORAGE + */ /////////////////////////////////////////////// + IterableMappingAddr.Map endowmentMultiSigs; address public implementationAddress; - address public proxyAdmin; + address proxyAdmin; IRegistrar registrar; - constructor(address _implementationAddress, address _proxyAdmin, address _registrar) { - require(_implementationAddress != address(0), "Invalid Address"); - require(_proxyAdmin != address(0), "Invalid Address"); - require(_registrar != address(0), "Invalid Address"); - - registrar = IRegistrar(_registrar); - implementationAddress = _implementationAddress; - emit ImplementationUpdated(_implementationAddress); - - proxyAdmin = _proxyAdmin; - emit ProxyAdminUpdated(_proxyAdmin); + constructor(address _implementationAddress, address _proxyAdmin, address registrarAddress) { + updateImplementation(_implementationAddress); + updateProxyAdmin(_proxyAdmin); + updateRegistrar(registrarAddress); } + /*//////////////////////////////////////////////// + MODIFIERS + */ /////////////////////////////////////////////// + modifier onlyAccountsContract() { RegistrarStorage.Config memory registrarConfig = registrar.queryConfig(); - require(msg.sender == registrarConfig.accountsContract, "Only Accounts Contract"); + if (msg.sender != registrarConfig.accountsContract) { + revert OnlyAccountsContract(); + } _; } - /* - * Public functions - */ - /// @dev Returns number of instantiations by creator. - /// @param creator Contract creator. - /// @return Returns number of instantiations by creator. - function getInstantiationCount(address creator) public view returns (uint256) { - return instantiations[creator].length; + /*//////////////////////////////////////////////// + IMPLEMENTATION + */ /////////////////////////////////////////////// + + /// @notice Get all EndowmentMultiSig proxy contract instantiations. + /// @return Array of instantiation addresses. + function getInstantiations() external view returns (address[] memory) { + return endowmentMultiSigs.keys; + } + + /// @notice Get stored registrar address. + /// @return address of the stored registrar. + function getRegistrar() external view returns (address) { + return address(registrar); + } + + /// @notice Get proxy admin address. + /// @return address of the proxy admin. + function getProxyAdmin() external view returns (address) { + return proxyAdmin; } /** @@ -63,7 +66,9 @@ contract EndowmentMultiSigFactory is IEndowmentMultiSigFactory, Ownable { * @param _implementationAddress The address of the new implementation */ function updateImplementation(address _implementationAddress) public onlyOwner { - require(_implementationAddress != address(0), "Invalid Address"); + if (!Validator.addressChecker(_implementationAddress)) { + revert InvalidAddress("_implementationAddress"); + } implementationAddress = _implementationAddress; emit ImplementationUpdated(_implementationAddress); } @@ -73,11 +78,25 @@ contract EndowmentMultiSigFactory is IEndowmentMultiSigFactory, Ownable { * @param _proxyAdmin The address of the new proxy admin */ function updateProxyAdmin(address _proxyAdmin) public onlyOwner { - require(_proxyAdmin != address(0), "Invalid Address"); + if (!Validator.addressChecker(_proxyAdmin)) { + revert InvalidAddress("_proxyAdmin"); + } proxyAdmin = _proxyAdmin; emit ProxyAdminUpdated(_proxyAdmin); } + /** + * @dev Updates the registrar address + * @param registrarAddress The address of the new registrar + */ + function updateRegistrar(address registrarAddress) public onlyOwner { + if (!Validator.addressChecker(registrarAddress)) { + revert InvalidAddress("registrarAddress"); + } + registrar = IRegistrar(registrarAddress); + emit RegistrarUpdated(registrarAddress); + } + /** @dev Create a new multisig wallet for an endowment * @param endowmentId the endowment id * @param emitterAddress the emitter of the multisig @@ -113,18 +132,15 @@ contract EndowmentMultiSigFactory is IEndowmentMultiSigFactory, Ownable { ); register(wallet); - // also store address of multisig in endowmentIdToMultisig - endowmentIdToMultisig[endowmentId] = wallet; } /* * Internal functions */ /// @dev Registers contract in factory registry. - /// @param instantiation Address of contract instantiation. + /// @param instantiation Address of EndowmentMultiSig proxy contract instantiation. function register(address instantiation) internal { - isInstantiation[instantiation] = true; - instantiations[msg.sender].push(instantiation); + IterableMappingAddr.set(endowmentMultiSigs, instantiation, true); emit ContractInstantiated(msg.sender, instantiation); } } diff --git a/contracts/multisigs/endowment-multisig/interfaces/IEndowmentMultiSigFactory.sol b/contracts/multisigs/endowment-multisig/interfaces/IEndowmentMultiSigFactory.sol index 9a2720375..43efbd98c 100644 --- a/contracts/multisigs/endowment-multisig/interfaces/IEndowmentMultiSigFactory.sol +++ b/contracts/multisigs/endowment-multisig/interfaces/IEndowmentMultiSigFactory.sol @@ -2,6 +2,23 @@ pragma solidity ^0.8.19; interface IEndowmentMultiSigFactory { + /*//////////////////////////////////////////////// + EVENTS + */ /////////////////////////////////////////////// + event ContractInstantiated(address sender, address instantiation); + event ImplementationUpdated(address implementationAddress); + event ProxyAdminUpdated(address admin); + event RegistrarUpdated(address registrar); + + /*//////////////////////////////////////////////// + ERRORS + */ /////////////////////////////////////////////// + error InvalidAddress(string param); + error OnlyAccountsContract(); + + /*//////////////////////////////////////////////// + EXTERNAL FUNCTIONS + */ //////////////////////////////////////////////// function create( uint256 endowmentId, address emitterAddress, @@ -14,5 +31,17 @@ interface IEndowmentMultiSigFactory { function updateProxyAdmin(address proxyAdminAddress) external; - function endowmentIdToMultisig(uint256 endowmentId) external returns (address); + function updateRegistrar(address registrarAddress) external; + + /// @notice Get all EndowmentMultiSig proxy contract instantiations. + /// @return Array of instantiation addresses. + function getInstantiations() external view returns (address[] memory); + + /// @notice Get stored registrar address. + /// @return address of the stored registrar. + function getRegistrar() external view returns (address); + + /// @notice Get proxy admin address. + /// @return address of the proxy admin. + function getProxyAdmin() external view returns (address); } diff --git a/tasks/deploy/deployRegistrar.ts b/tasks/deploy/deployRegistrar.ts index 0c1b180f8..3489fde27 100644 --- a/tasks/deploy/deployRegistrar.ts +++ b/tasks/deploy/deployRegistrar.ts @@ -106,6 +106,11 @@ task( apTeamSignerPkey: taskArgs.apTeamSignerPkey, yes: true, }); + await hre.run("manage:endowmentMultiSigFactory:updateRegistrar", { + registrar: registrar.proxy.contract.address, + apTeamSignerPkey: taskArgs.apTeamSignerPkey, + yes: true, + }); if (!isLocalNetwork(hre) && !taskArgs.skipVerify) { await verify(hre, registrar.implementation); diff --git a/tasks/helpers/submitMultiSigTx.ts b/tasks/helpers/submitMultiSigTx.ts index 8519e7148..517e3ace7 100644 --- a/tasks/helpers/submitMultiSigTx.ts +++ b/tasks/helpers/submitMultiSigTx.ts @@ -5,19 +5,19 @@ import {filterEvents, logger} from "utils"; /** * Submits a transaction to the designated Multisig contract and executes it if possible. * @param msAddress address of the Multisig contract - * @param owner signer representing one of the Multisig owners + * @param msOwner signer representing one of the Multisig owners * @param destination transaction target address * @param data transaction data payload * @returns boolean value indicating whether the transaction was executed or not (i.e. is pending confirmation by other owners) */ export async function submitMultiSigTx( msAddress: string, - owner: Signer, + msOwner: Signer, destination: string, data: BytesLike ): Promise { logger.out(`Submitting transaction to Multisig at address: ${msAddress}...`); - const multisig = IMultiSigGeneric__factory.connect(msAddress, owner); + const multisig = IMultiSigGeneric__factory.connect(msAddress, msOwner); const feeData = await multisig.provider.getFeeData(); const tx = await multisig.submitTransaction(destination, 0, data, "0x", { gasPrice: feeData.gasPrice ?? undefined, diff --git a/tasks/manage/changeProxyAdmin.ts b/tasks/manage/changeProxyAdmin.ts index c2ae1c92c..ebeae0775 100644 --- a/tasks/manage/changeProxyAdmin.ts +++ b/tasks/manage/changeProxyAdmin.ts @@ -1,150 +1,74 @@ -import {Signer} from "ethers"; import {task} from "hardhat/config"; -import {HardhatRuntimeEnvironment} from "hardhat/types"; -import { - ITransparentUpgradeableProxy__factory, - OwnershipFacet__factory, - ProxyContract__factory, -} from "typechain-types"; -import {AddressObj, confirmAction, getAddresses, getProxyAdminOwner, logger} from "utils"; +import {ITransparentUpgradeableProxy__factory, ProxyContract__factory} from "typechain-types"; +import {confirmAction, getAddresses, getProxyAdminOwner, logger} from "utils"; import {submitMultiSigTx} from "../helpers"; type TaskArgs = { - apTeamSignerPkey?: string; - proxyAdminPkey: string; - newProxyAdmin: string; + to?: string; + proxy: string; + proxyAdminPkey?: string; yes: boolean; }; -task("manage:changeProxyAdmin", "Will update the proxy admin for all proxy contracts") +task("manage:changeProxyAdmin", "Will update the proxy admin the target proxy contract") .addOptionalParam( - "proxyAdminPkey", - "The pkey for one of the current ProxyAdminMultiSig's owners." - ) - .addParam( - "newProxyAdmin", - "New admin address. Make sure to use an address of an account listed in the hardhat configuration for the target network." + "to", + "New proxy admin address. Make sure to use an address of an account you control. Will do a local lookup from contract-address.json if none is provided." ) + .addParam("proxy", "Target Proxy Contract. Must be owned by the ProxyAdminMultiSig.") .addOptionalParam( - "apTeamSignerPkey", - "If running on prod, provide a pkey for a valid APTeam Multisig Owner." + "proxyAdminPkey", + "The pkey for one of the current ProxyAdminMultiSig's owners." ) .addFlag("yes", "Automatic yes to prompt.") .setAction(async (taskArgs: TaskArgs, hre) => { try { - const isConfirmed = - taskArgs.yes || - (await confirmAction(`Change all contracts' admin to: ${taskArgs.newProxyAdmin}`)); - if (!isConfirmed) { - return logger.out("Confirmation denied.", logger.Level.Warn); - } - - const proxyAdminOwner = await getProxyAdminOwner(hre, taskArgs.proxyAdminPkey); - - if ((await proxyAdminOwner.getAddress()) === taskArgs.newProxyAdmin) { - return logger.out(`"${taskArgs.newProxyAdmin}" is already the proxy admin.`); - } + logger.divider(); const addresses = await getAddresses(hre); + const targetAddress = taskArgs.to || addresses.multiSig.proxyAdmin; - await transferAccountOwnership(proxyAdminOwner, taskArgs.newProxyAdmin, addresses, hre); - - await changeProxiesAdmin(proxyAdminOwner, taskArgs.newProxyAdmin, addresses, hre); - - await hre.run("manage:registrar:updateConfig", { - proxyAdmin: taskArgs.newProxyAdmin, - apTeamSignerPkey: taskArgs.apTeamSignerPkey, - yes: true, - }); - } catch (error) { - logger.out(error, logger.Level.Error); - } - }); + logger.out(`Change admin of proxy contract at "${taskArgs.proxy}" to: ${targetAddress}...`); -async function transferAccountOwnership( - proxyAdminOwner: Signer, - newProxyAdmin: string, - addresses: AddressObj, - hre: HardhatRuntimeEnvironment -) { - try { - logger.out("Transferring Account diamond ownership..."); - - const ownershipFacet = OwnershipFacet__factory.connect( - addresses.accounts.diamond, - hre.ethers.provider - ); - const data = ownershipFacet.interface.encodeFunctionData("transferOwnership", [newProxyAdmin]); - - const isExecuted = await submitMultiSigTx( - addresses.multiSig.proxyAdmin, - proxyAdminOwner, - ownershipFacet.address, - data - ); - - if (isExecuted) { - const newOwner = await ownershipFacet.owner(); - logger.out(`Owner is now set to: ${newOwner}`); - } - } catch (error) { - logger.out(`Failed to change admin for Account diamond, reason: ${error}`, logger.Level.Error); - } -} + const isConfirmed = taskArgs.yes || (await confirmAction()); + if (!isConfirmed) { + return logger.out("Confirmation denied.", logger.Level.Warn); + } -/** - * Edge case: changing proxy admin address for all contracts that implement `fallback` function - * will never revert, but will nevertheless NOT update the admin. - */ -async function changeProxiesAdmin( - proxyAdminOwner: Signer, - newProxyAdmin: string, - addresses: AddressObj, - hre: HardhatRuntimeEnvironment -) { - logger.out("Reading proxy contract addresses..."); + const proxyAdminOwner = await getProxyAdminOwner(hre, taskArgs.proxyAdminPkey); - const proxies = extractProxyContractAddresses("", addresses); + const proxyContract = ProxyContract__factory.connect(taskArgs.proxy, hre.ethers.provider); + const curAdmin = await proxyContract.getAdmin(); - for (const proxy of proxies) { - try { - logger.divider(); - logger.out(`Changing admin for ${proxy.name}...`); + if (curAdmin === targetAddress) { + return logger.out(`"${targetAddress}" is already the proxy admin.`); + } - const proxyContract = ProxyContract__factory.connect(proxy.address, hre.ethers.provider); - const curAdmin = await proxyContract.getAdmin(); logger.out(`Current Admin: ${curAdmin}`); + // submitting the Tx const data = ITransparentUpgradeableProxy__factory.createInterface().encodeFunctionData( "changeAdmin", - [newProxyAdmin] + [targetAddress] ); + // make sure to use current proxy admin MultiSig, as the task might be run with the proxyAdmin address + // already updated in contract-address.json const isExecuted = await submitMultiSigTx( - addresses.multiSig.proxyAdmin, + curAdmin, proxyAdminOwner, - proxy.address, + proxyContract.address, data ); if (isExecuted) { - const proxyContract = ProxyContract__factory.connect(proxy.address, hre.ethers.provider); - const newAdmin = await proxyContract.getAdmin(); - logger.out(`New admin: ${newAdmin}`); + const newProxyAdmin = await proxyContract.getAdmin(); + if (newProxyAdmin !== targetAddress) { + throw new Error( + `Unexpected: expected new proxy admin "${targetAddress}", but got "${newProxyAdmin}"` + ); + } } } catch (error) { - logger.out(`Failed to change admin, reason: ${error}`, logger.Level.Error); + logger.out(error, logger.Level.Error); } - } -} - -function extractProxyContractAddresses(key: string, value: any): {name: string; address: string}[] { - if (!value || typeof value !== "object") { - return []; - } - - if ("proxy" in value && !!value.proxy) { - return [{name: key, address: value.proxy}]; - } - - return Object.entries(value).flatMap(([key, val]) => extractProxyContractAddresses(key, val)); -} + }); diff --git a/tasks/manage/changeProxyAdminForAll.ts b/tasks/manage/changeProxyAdminForAll.ts new file mode 100644 index 000000000..7ac3d8106 --- /dev/null +++ b/tasks/manage/changeProxyAdminForAll.ts @@ -0,0 +1,145 @@ +import {Signer} from "ethers"; +import {task} from "hardhat/config"; +import {HardhatRuntimeEnvironment} from "hardhat/types"; +import {OwnershipFacet__factory} from "typechain-types"; +import {AddressObj, confirmAction, getAddresses, getProxyAdminOwner, logger} from "utils"; +import {submitMultiSigTx} from "../helpers"; + +type TaskArgs = { + to?: string; + apTeamSignerPkey?: string; + proxyAdminPkey?: string; + yes: boolean; +}; + +task("manage:changeProxyAdminForAll", "Will update the proxy admin for all proxy contracts") + .addOptionalParam( + "to", + "New proxy admin address. Make sure to use an address of an account you control. Will do a local lookup from contract-address.json if none is provided." + ) + .addOptionalParam( + "proxyAdminPkey", + "The pkey for one of the current ProxyAdminMultiSig's owners." + ) + .addOptionalParam( + "apTeamSignerPkey", + "If running on prod, provide a pkey for a valid APTeam Multisig Owner." + ) + .addFlag("yes", "Automatic yes to prompt.") + .setAction(async (taskArgs: TaskArgs, hre) => { + try { + logger.divider(); + + const addresses = await getAddresses(hre); + const toAddress = taskArgs.to || addresses.multiSig.proxyAdmin; + + logger.out(`Change all contracts' admin to: ${toAddress}...`); + + const isConfirmed = taskArgs.yes || (await confirmAction()); + if (!isConfirmed) { + return logger.out("Confirmation denied.", logger.Level.Warn); + } + + const proxyAdminOwner = await getProxyAdminOwner(hre, taskArgs.proxyAdminPkey); + + await transferAccountOwnership(proxyAdminOwner, toAddress, addresses, hre); + + await changeProxiesAdmin(taskArgs.proxyAdminPkey, toAddress, addresses, hre); + + await hre.run("manage:endowmentMultiSigFactory:updateProxyAdmin", { + to: toAddress, + apTeamSignerPkey: taskArgs.apTeamSignerPkey, + proxyAdminPkey: taskArgs.proxyAdminPkey, + yes: true, + }); + + await hre.run("manage:registrar:updateConfig", { + proxyAdmin: toAddress, + apTeamSignerPkey: taskArgs.apTeamSignerPkey, + yes: true, + }); + } catch (error) { + logger.out(error, logger.Level.Error); + } + }); + +async function transferAccountOwnership( + proxyAdminOwner: Signer, + newProxyAdmin: string, + addresses: AddressObj, + hre: HardhatRuntimeEnvironment +) { + try { + logger.out("Transferring Account diamond ownership (admin)..."); + + const ownershipFacet = OwnershipFacet__factory.connect( + addresses.accounts.diamond, + hre.ethers.provider + ); + const curOwner = await ownershipFacet.owner(); + logger.out(`Current owner: ${curOwner}`); + + if (curOwner === newProxyAdmin) { + return logger.out(`"${newProxyAdmin}" is already the owner.`); + } + + const data = ownershipFacet.interface.encodeFunctionData("transferOwnership", [newProxyAdmin]); + + // make sure to use current proxy admin MultiSig, as the task might be run with the proxyAdmin address + // already updated in contract-address.json + const isExecuted = await submitMultiSigTx( + curOwner, + proxyAdminOwner, + ownershipFacet.address, + data + ); + + if (isExecuted) { + const newOwner = await ownershipFacet.owner(); + if (newOwner !== newProxyAdmin) { + throw new Error( + `Unexpected: expected new proxy admin "${newProxyAdmin}", but got "${newOwner}"` + ); + } + logger.out(`Owner is now set to: ${newOwner}`); + } + } catch (error) { + logger.out(`Failed to change admin for Account diamond, reason: ${error}`, logger.Level.Error); + } +} + +/** + * Edge case: changing proxy admin address with a non-owning signer for all contracts that + * implement `fallback` function will never revert, but will nevertheless NOT update the admin. + */ +async function changeProxiesAdmin( + proxyAdminPkey: string | undefined, + newProxyAdmin: string, + addresses: AddressObj, + hre: HardhatRuntimeEnvironment +) { + logger.divider(); + logger.out("Reading proxy contract addresses..."); + const proxies = extractProxyContractAddresses("", addresses); + + for (const proxy of proxies) { + await hre.run("manage:changeProxyAdmin", { + to: newProxyAdmin, + proxy: proxy.address, + proxyAdminPkey: proxyAdminPkey, + yes: true, + }); + } +} + +function extractProxyContractAddresses(key: string, value: any): {name: string; address: string}[] { + if (!value || typeof value !== "object") { + return []; + } + + if ("proxy" in value && !!value.proxy) { + return [{name: key, address: value.proxy}]; + } + + return Object.entries(value).flatMap(([key, val]) => extractProxyContractAddresses(key, val)); +} diff --git a/tasks/manage/endowmentMultiSigFactory/index.ts b/tasks/manage/endowmentMultiSigFactory/index.ts new file mode 100644 index 000000000..ff6de0aa7 --- /dev/null +++ b/tasks/manage/endowmentMultiSigFactory/index.ts @@ -0,0 +1,2 @@ +import "./updateProxyAdmin"; +import "./updateRegistrar"; diff --git a/tasks/manage/endowmentMultiSigFactory/updateProxyAdmin/index.ts b/tasks/manage/endowmentMultiSigFactory/updateProxyAdmin/index.ts new file mode 100644 index 000000000..f1540f14a --- /dev/null +++ b/tasks/manage/endowmentMultiSigFactory/updateProxyAdmin/index.ts @@ -0,0 +1 @@ +import "./updateProxyAdmin"; diff --git a/tasks/manage/endowmentMultiSigFactory/updateProxyAdmin/updateEndowmentMultiSigFactory.ts b/tasks/manage/endowmentMultiSigFactory/updateProxyAdmin/updateEndowmentMultiSigFactory.ts new file mode 100644 index 000000000..80af32723 --- /dev/null +++ b/tasks/manage/endowmentMultiSigFactory/updateProxyAdmin/updateEndowmentMultiSigFactory.ts @@ -0,0 +1,52 @@ +import {HardhatRuntimeEnvironment} from "hardhat/types"; +import {submitMultiSigTx} from "tasks/helpers"; +import {IEndowmentMultiSigFactory__factory} from "typechain-types"; +import {getAPTeamOwner, getAddresses, logger} from "utils"; + +/** + * @param newProxyAdmin Address of the new proxy admin. + * @param apTeamSignerPkey Private key of one of the APTeamMultiSig owners + * @param hre @see HardhatRuntimeEnvironment + * @returns boolean value indicating whether proxy admin was updated to `newProxyAdmin` or not + */ +export default async function updateEndowmentMultiSigFactory( + newProxyAdmin: string, + apTeamSignerPkey: string | undefined, + hre: HardhatRuntimeEnvironment +): Promise { + const addresses = await getAddresses(hre); + + const endowmentMultiSigFactory = IEndowmentMultiSigFactory__factory.connect( + addresses.multiSig.endowment.factory, + hre.ethers.provider + ); + const oldProxyAdmin = await endowmentMultiSigFactory.getProxyAdmin(); + if (oldProxyAdmin === newProxyAdmin) { + logger.out(`"${newProxyAdmin}" is already the proxy admin.`); + return true; + } + + const apTeamOwner = await getAPTeamOwner(hre, apTeamSignerPkey); + + // submitting the Tx + const data = endowmentMultiSigFactory.interface.encodeFunctionData("updateProxyAdmin", [ + newProxyAdmin, + ]); + const isExecuted = await submitMultiSigTx( + addresses.multiSig.apTeam.proxy, + apTeamOwner, + endowmentMultiSigFactory.address, + data + ); + if (!isExecuted) { + return false; + } + const updatedProxyAdmin = await endowmentMultiSigFactory.getProxyAdmin(); + if (updatedProxyAdmin !== newProxyAdmin) { + throw new Error( + `Unexpected: expected new proxy admin "${newProxyAdmin}", but got "${updatedProxyAdmin}"` + ); + } + + return true; +} diff --git a/tasks/manage/endowmentMultiSigFactory/updateProxyAdmin/updateEndowmentProxiesAdmin.ts b/tasks/manage/endowmentMultiSigFactory/updateProxyAdmin/updateEndowmentProxiesAdmin.ts new file mode 100644 index 000000000..78ded885a --- /dev/null +++ b/tasks/manage/endowmentMultiSigFactory/updateProxyAdmin/updateEndowmentProxiesAdmin.ts @@ -0,0 +1,97 @@ +import {HardhatRuntimeEnvironment} from "hardhat/types"; +import { + IEndowmentMultiSigFactory__factory, + ProxyAdminMultiSig__factory, + ProxyContract__factory, +} from "typechain-types"; +import {getAddresses, getProxyAdminOwner, logger} from "utils"; + +class CheckError extends Error {} + +export default async function updateEndowmentProxiesAdmin( + targetAddress: string, + proxyAdminPkey: string | undefined, + hre: HardhatRuntimeEnvironment +) { + const addresses = await getAddresses(hre); + const proxyAdminOwner = await getProxyAdminOwner(hre, proxyAdminPkey); + + const endowmentMultiSigFactory = IEndowmentMultiSigFactory__factory.connect( + addresses.multiSig.endowment.factory, + hre.ethers.provider + ); + const endowmentProxies = await endowmentMultiSigFactory.getInstantiations(); + + for (const endowmentProxy of endowmentProxies) { + try { + await checkIfManagedByProxyAdmin(endowmentProxy, await proxyAdminOwner.getAddress(), hre); + + await hre.run("manage:changeProxyAdmin", { + to: targetAddress, + proxy: endowmentProxy, + proxyAdminPkey: proxyAdminPkey, + yes: true, + }); + } catch (error) { + const logLevel = error instanceof CheckError ? logger.Level.Warn : logger.Level.Error; + logger.out(error, logLevel); + } + } +} + +/** + * It is possible the Endowment's proxy is not managed by our ProxyAdminMultiSig. + * In those cases we should skip submitting any Txs to avoid wasting gas. + * + * This is just a rudimendaty check that will throw if `curAdmin` is not really a ProxyAdminMultiSig + * in which case it'll throw and stop the execution. We check this by assuming that if the address + * is connected to a contract that contains the `submitTransaction` and `isOwner` functions and also + * that the currently used proxy admin owner is really one of the owners of of the proxy's admin contract, + * then it's most likely that the said proxy admin contract is an instance of ProxyAdminMultiSig. + * + * @param proxy Address of the proxy to check + * @param proxyAdminOwner Address of one of the current proxy admin's owners + * @param hre HardhatRuntimeEnvironment + */ +async function checkIfManagedByProxyAdmin( + proxy: string, + proxyAdminOwner: string, + hre: HardhatRuntimeEnvironment +): Promise { + const proxyContract = ProxyContract__factory.connect(proxy, hre.ethers.provider); + const curAdmin = await proxyContract.getAdmin(); + + const proxyAdminMS = ProxyAdminMultiSig__factory.connect(curAdmin, hre.ethers.provider); + const bytecode = await hre.ethers.provider.getCode(curAdmin); + + // No code : "0x" then functionA is definitely not there + if (bytecode.length <= 2) { + throw new CheckError("Skipping: admin address has no code"); + } + + // If the bytecode doesn't include the function selector submitTransaction() + // is definitely not present + const submitTransactionSelector = proxyAdminMS.interface.getSighash( + proxyAdminMS.interface.functions["submitTransaction(address,uint256,bytes,bytes)"] + ); + if (!bytecode.includes(submitTransactionSelector.slice(2))) { + throw new CheckError( + "Skipping: not a ProxyAdminMultiSig - no submitTransaction() function selector in bytecode" + ); + } + + const isOwnersSelector = proxyAdminMS.interface.getSighash( + proxyAdminMS.interface.functions["isOwner(address)"] + ); + if (!bytecode.includes(isOwnersSelector.slice(2))) { + throw new CheckError( + "Skipping: not a ProxyAdminMultiSig - no isOwner() function selector in bytecode" + ); + } + + if (!(await proxyAdminMS.isOwner(proxyAdminOwner))) { + throw new CheckError( + `Skipping: "${proxyAdminOwner}" is not one of the target contract's owners` + ); + } +} diff --git a/tasks/manage/endowmentMultiSigFactory/updateProxyAdmin/updateProxyAdmin.ts b/tasks/manage/endowmentMultiSigFactory/updateProxyAdmin/updateProxyAdmin.ts new file mode 100644 index 000000000..bfb31d74c --- /dev/null +++ b/tasks/manage/endowmentMultiSigFactory/updateProxyAdmin/updateProxyAdmin.ts @@ -0,0 +1,60 @@ +import {task} from "hardhat/config"; +import {confirmAction, getAddresses, logger} from "utils"; +import updateEndowmentMultiSigFactory from "./updateEndowmentMultiSigFactory"; +import updateEndowmentProxiesAdmin from "./updateEndowmentProxiesAdmin"; + +type TaskArgs = { + to?: string; + apTeamSignerPkey?: string; + proxyAdminPkey: string; + yes: boolean; +}; + +task( + "manage:endowmentMultiSigFactory:updateProxyAdmin", + "Will update the EndowmentMultiSigFactory's proxy admin address" +) + .addOptionalParam( + "to", + "New proxy admin address. Make sure to use an address of an account you control. Will do a local lookup from contract-address.json if none is provided." + ) + .addOptionalParam( + "apTeamSignerPkey", + "If running on prod, provide a pkey for a valid APTeam Multisig Owner." + ) + .addOptionalParam( + "proxyAdminPkey", + "The pkey for one of the current ProxyAdminMultiSig's owners." + ) + .addFlag("yes", "Automatic yes to prompt.") + .setAction(async (taskArgs: TaskArgs, hre) => { + try { + logger.divider(); + + const addresses = await getAddresses(hre); + const targetAddress = taskArgs.to || addresses.multiSig.proxyAdmin; + + logger.out(`Updating EndowmentMultiSigFactory's proxy admin to ${targetAddress}...`); + + const isConfirmed = taskArgs.yes || (await confirmAction()); + if (!isConfirmed) { + return logger.out("Confirmation denied.", logger.Level.Warn); + } + + // submit Tx to update proxy admin in EndowmentMultiSigFactory's state + const isUpdated = await updateEndowmentMultiSigFactory( + targetAddress, + taskArgs.apTeamSignerPkey, + hre + ); + // wait for proxy admin update Tx to be executed before updating existing EndowmentMultiSigs + if (!isUpdated) { + return; + } + + // update existing EndowmentMultiSig Proxies' admins + await updateEndowmentProxiesAdmin(targetAddress, taskArgs.proxyAdminPkey, hre); + } catch (error) { + logger.out(error, logger.Level.Error); + } + }); diff --git a/tasks/manage/endowmentMultiSigFactory/updateRegistrar.ts b/tasks/manage/endowmentMultiSigFactory/updateRegistrar.ts new file mode 100644 index 000000000..e6fa46662 --- /dev/null +++ b/tasks/manage/endowmentMultiSigFactory/updateRegistrar.ts @@ -0,0 +1,71 @@ +import {task} from "hardhat/config"; +import {submitMultiSigTx} from "tasks/helpers"; +import {IEndowmentMultiSigFactory__factory} from "typechain-types"; +import {confirmAction, getAPTeamOwner, getAddresses, logger} from "utils"; + +type TaskArgs = { + apTeamSignerPkey?: string; + registrar?: string; + yes: boolean; +}; + +task( + "manage:endowmentMultiSigFactory:updateRegistrar", + "Will update the EndowmentMultiSigFactory's registrar address" +) + .addOptionalParam( + "registrar", + "Registrar contract address. Will do a local lookup from contract-address.json if none is provided." + ) + .addOptionalParam( + "apTeamSignerPkey", + "If running on prod, provide a pkey for a valid APTeam Multisig Owner." + ) + .addFlag("yes", "Automatic yes to prompt.") + .setAction(async (taskArgs: TaskArgs, hre) => { + try { + logger.divider(); + + const addresses = await getAddresses(hre); + const registrarAddress = taskArgs.registrar || addresses.registrar.proxy; + + logger.out(`Updating EndowmentMultiSigFactory's registrar address to ${registrarAddress}...`); + + const isConfirmed = taskArgs.yes || (await confirmAction()); + if (!isConfirmed) { + return logger.out("Confirmation denied.", logger.Level.Warn); + } + + const endowmentMultiSigFactory = IEndowmentMultiSigFactory__factory.connect( + addresses.multiSig.endowment.factory, + hre.ethers.provider + ); + if ((await endowmentMultiSigFactory.getRegistrar()) === registrarAddress) { + return logger.out(`"${registrarAddress}" is already the stored registrar address.`); + } + + const apTeamOwner = await getAPTeamOwner(hre, taskArgs.apTeamSignerPkey); + + // submitting the Tx + const data = endowmentMultiSigFactory.interface.encodeFunctionData("updateRegistrar", [ + registrarAddress, + ]); + const isExecuted = await submitMultiSigTx( + addresses.multiSig.apTeam.proxy, + apTeamOwner, + endowmentMultiSigFactory.address, + data + ); + if (!isExecuted) { + return; + } + const newRegAddr = await endowmentMultiSigFactory.getRegistrar(); + if (newRegAddr !== registrarAddress) { + throw new Error( + `Unexpected: expected new registrar address "${registrarAddress}", but got "${newRegAddr}"` + ); + } + } catch (error) { + logger.out(error, logger.Level.Error); + } + }); diff --git a/tasks/manage/index.ts b/tasks/manage/index.ts index d5f32cdee..cf76f1370 100644 --- a/tasks/manage/index.ts +++ b/tasks/manage/index.ts @@ -1,7 +1,9 @@ import "./accounts"; import "./addMultisigOwners"; import "./changeProxyAdmin"; +import "./changeProxyAdminForAll"; import "./charityApplications"; +import "./endowmentMultiSigFactory"; import "./createEndowment"; import "./createIndexFund"; import "./gasFwdFactory"; diff --git a/tasks/manage/registrar/updateConfig.ts b/tasks/manage/registrar/updateConfig.ts index 9078a646f..f458560be 100644 --- a/tasks/manage/registrar/updateConfig.ts +++ b/tasks/manage/registrar/updateConfig.ts @@ -114,10 +114,10 @@ task("manage:registrar:updateConfig", "Will update Accounts Diamond config") ) .addFlag("yes", "Automatic yes to prompt.") .setAction(async (taskArgs: TaskArgs, hre) => { - logger.divider(); - logger.out("Updating Registrar config..."); - try { + logger.divider(); + logger.out("Updating Registrar config..."); + const addresses = await getAddresses(hre); const apTeamOwner = await getAPTeamOwner(hre, taskArgs.apTeamSignerPkey); @@ -140,7 +140,7 @@ task("manage:registrar:updateConfig", "Will update Accounts Diamond config") logger.out("Config data to update:"); logger.out(updateConfigRequest); - const isConfirmed = taskArgs.yes || (await confirmAction(`Updating Registrar config...`)); + const isConfirmed = taskArgs.yes || (await confirmAction()); if (!isConfirmed) { return logger.out("Confirmation denied.", logger.Level.Warn); } diff --git a/tasks/upgrade/upgradeEndowmentMultiSigEmitter.ts b/tasks/upgrade/upgradeEndowmentMultiSigEmitter.ts index 455cefab7..049f39367 100644 --- a/tasks/upgrade/upgradeEndowmentMultiSigEmitter.ts +++ b/tasks/upgrade/upgradeEndowmentMultiSigEmitter.ts @@ -17,7 +17,6 @@ import { } from "utils"; type TaskArgs = { - factory?: string; skipVerify: boolean; yes: boolean; proxyAdminPkey?: string;