From aae019688f2e0ec435dbe292815cc8573a003061 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Thu, 5 Sep 2024 20:22:30 +0300 Subject: [PATCH 1/2] Add support of multiple proxy admins --- src/proxyAdmin.ts | 61 ++++++++++++++++ src/submitters/auto-submitter.ts | 16 +++-- src/upgrader.ts | 119 ++++++++++++------------------- 3 files changed, 117 insertions(+), 79 deletions(-) create mode 100644 src/proxyAdmin.ts diff --git a/src/proxyAdmin.ts b/src/proxyAdmin.ts new file mode 100644 index 0000000..14be796 --- /dev/null +++ b/src/proxyAdmin.ts @@ -0,0 +1,61 @@ +import {AddressLike, Contract, Transaction} from "ethers"; +import {ethers, upgrades} from "hardhat"; +import chalk from "chalk"; + +export const getProxyAdmin = async (proxy: AddressLike) => { + const proxyAdminAddress = await upgrades.erc1967.getAdminAddress( + await ethers.resolveAddress(proxy) + ); + const generalProxyAdminAbi = [ + "function UPGRADE_INTERFACE_VERSION() view returns (string)", + "function upgrade(address,address)", + "function upgradeAndCall(address,address,bytes) payable", + "function owner() view returns (address)" + ]; + return new ethers.Contract( + proxyAdminAddress, + generalProxyAdminAbi, + await ethers.provider.getSigner() + ); +} + +export const isNewProxyAdmin = async (proxyAdmin: Contract) => { + try { + console.log(chalk.gray(`ProxyAdmin version ${ + // This function name is set in external library + // eslint-disable-next-line new-cap + await proxyAdmin.UPGRADE_INTERFACE_VERSION() + }`)); + return true; + } catch (error) { + console.log(chalk.gray("Use old ProxyAdmin")); + return false; + } +} + +export const getUpgradeTransaction = async (proxy: AddressLike, implementation: AddressLike) => { + const proxyAdmin = await getProxyAdmin(proxy); + if (await isNewProxyAdmin(proxyAdmin)) { + return Transaction.from({ + "data": proxyAdmin.interface.encodeFunctionData( + "upgradeAndCall", + [ + await ethers.resolveAddress(proxy), + await ethers.resolveAddress(implementation), + "0x" + ] + ), + "to": await ethers.resolveAddress(proxyAdmin) + }); + } + return Transaction.from({ + "data": proxyAdmin.interface.encodeFunctionData( + "upgrade", + [ + await ethers.resolveAddress(proxy), + await ethers.resolveAddress(implementation), + ] + ), + "to": await ethers.resolveAddress(proxyAdmin) + }); +} diff --git a/src/submitters/auto-submitter.ts b/src/submitters/auto-submitter.ts index 5427599..2f1088e 100644 --- a/src/submitters/auto-submitter.ts +++ b/src/submitters/auto-submitter.ts @@ -7,7 +7,6 @@ import { } from "./safe-ima-legacy-marionette-submitter"; import {SafeSubmitter} from "./safe-submitter"; import {Submitter} from "./submitter"; - import {Upgrader} from "../upgrader"; import chalk from "chalk"; import hre from "hardhat"; @@ -16,6 +15,14 @@ import {skaleContracts} from "@skalenetwork/skale-contracts-ethers-v6"; export class AutoSubmitter extends Submitter { name = "Auto Submitter"; + upgrader: Upgrader + + constructor ( + upgrader: Upgrader + ) { + super(); + this.upgrader = upgrader + } static marionetteInterface = [ { @@ -35,15 +42,14 @@ export class AutoSubmitter extends Submitter { async submit (transactions: Transaction[]) { console.log(`Submit via ${this.name}`); - const submitter = await AutoSubmitter.getSubmitter(); + const submitter = await this.getSubmitter(); await submitter.submit(transactions); } // Private - private static async getSubmitter () { - const proxyAdmin = await Upgrader.getProxyAdmin(); - const owner = await proxyAdmin.owner(); + private async getSubmitter () { + const owner = await this.upgrader.getOwner(); if (await hre.ethers.provider.getCode(owner) === "0x") { console.log("Owner is not a contract"); return new EoaSubmitter(); diff --git a/src/upgrader.ts b/src/upgrader.ts index 444032d..b244fcc 100644 --- a/src/upgrader.ts +++ b/src/upgrader.ts @@ -1,7 +1,7 @@ -import {Contract, ContractFactory, Transaction} from "ethers"; +import {ContractFactory, Transaction} from "ethers"; import {ContractToUpgrade, Project} from "./types/upgrader"; -import {Manifest, getImplementationAddress} from "@openzeppelin/upgrades-core"; import {ethers, network, upgrades} from "hardhat"; +import {getProxyAdmin, getUpgradeTransaction} from "./proxyAdmin"; import {AutoSubmitter} from "./submitters/auto-submitter"; import {EXIT_CODES} from "./exitCodes"; import {Instance} from "@skalenetwork/skale-contracts-ethers-v6"; @@ -11,6 +11,7 @@ import {Submitter} from "./submitters/submitter"; import chalk from "chalk"; import {promises as fs} from "fs"; import {getContractFactoryAndUpdateManifest} from "./contractFactory"; +import {getImplementationAddress} from "@openzeppelin/upgrades-core"; import {getVersion} from "./version"; import {verify} from "./verification"; @@ -25,18 +26,18 @@ const deployTimeout = 60e4; export abstract class Upgrader { - instance: Instance; - targetVersion: string; - contractNamesToUpgrade: string[]; - projectName: string; - transactions: Transaction[]; - submitter: Submitter; - nonceProvider?: NonceProvider; - deploySemaphore: Semaphore; + private instance: Instance; + private targetVersion: string; + private contractNamesToUpgrade: string[]; + private projectName: string; + private transactions: Transaction[]; + private submitter: Submitter; + private nonceProvider?: NonceProvider; + private deploySemaphore: Semaphore; constructor ( project: Project, - submitter: Submitter = new AutoSubmitter() + submitter?: Submitter ) { this.targetVersion = project.version; if (!project.version.includes("-")) { @@ -46,7 +47,7 @@ export abstract class Upgrader { this.contractNamesToUpgrade = project.contractNamesToUpgrade; this.projectName = project.name; this.transactions = []; - this.submitter = submitter; + this.submitter = submitter ?? new AutoSubmitter(this); this.deploySemaphore = new Semaphore(maxSimultaneousDeployments); } @@ -69,8 +70,7 @@ export abstract class Upgrader { await this.callDeployNewContracts(); const contractsToUpgrade = await this.deployNewImplementations(); await this.switchToNewImplementations( - contractsToUpgrade, - await Upgrader.getProxyAdmin() + contractsToUpgrade ); await this.callInitialize(); // Write version @@ -81,23 +81,29 @@ export abstract class Upgrader { console.log("Done"); } - static async getProxyAdmin() { - const manifest = await Manifest.forNetwork(network.provider); - const adminDeployment = await manifest.getAdmin(); - if (!adminDeployment) { - throw new Error("Can't load ProxyAdmin address"); - } - const generalProxyAdminAbi = [ - "function UPGRADE_INTERFACE_VERSION() view returns (string)", - "function upgrade(address,address)", - "function upgradeAndCall(address,address,bytes) payable", - "function owner() view returns (address)" - ]; - return new ethers.Contract( - adminDeployment.address, - generalProxyAdminAbi, - await ethers.provider.getSigner() + async getOwner() { + const proxyAddresses = await Promise.all( + this.contractNamesToUpgrade.map( + (contract) => this.instance.getContractAddress(contract), + this + ) + ); + const admins = await Promise.all( + proxyAddresses.map( + (proxy) => getProxyAdmin(proxy) + ) + ); + const owners = await Promise.all( + admins.map( + (admin) => admin.owner() as Promise + ) ); + return owners.reduce( (owner1, owner2) => { + if (owner1 !== owner2) { + throw Error("Proxies have different owners"); + } + return owner1; + }) } // Private @@ -148,42 +154,21 @@ export abstract class Upgrader { } private async switchToNewImplementations ( - contractsToUpgrade: ContractToUpgrade[], - proxyAdmin: Contract + contractsToUpgrade: ContractToUpgrade[] ) { - const proxyAdminAddress = await proxyAdmin.getAddress(); - const newProxyAdmin = await Upgrader.isNewProxyAdmin(proxyAdmin); - for (const contract of contractsToUpgrade) { + const upgradeTransactions = await Promise.all( + contractsToUpgrade.map( + (contract) => getUpgradeTransaction(contract.proxyAddress, contract.implementationAddress) + ) + ); + contractsToUpgrade.forEach((contract, index) => { const infoMessage = `Prepare transaction to upgrade ${contract.name}` + ` at ${contract.proxyAddress}` + ` to ${contract.implementationAddress}`; console.log(chalk.yellowBright(infoMessage)); - let transaction = Transaction.from({ - "data": proxyAdmin.interface.encodeFunctionData( - "upgradeAndCall", - [ - contract.proxyAddress, - contract.implementationAddress, - "0x" - ] - ), - "to": proxyAdminAddress - }); - if (!newProxyAdmin) { - transaction = Transaction.from({ - "data": proxyAdmin.interface.encodeFunctionData( - "upgrade", - [ - contract.proxyAddress, - contract.implementationAddress - ] - ), - "to": proxyAdminAddress - }); - } - this.transactions.push(transaction); - } + this.transactions.push(upgradeTransactions[index]); + }); } private async deployNewImplementations () { @@ -280,18 +265,4 @@ export abstract class Upgrader { console.log(chalk.yellow(cannotCheckMessage)); } } - - private static async isNewProxyAdmin(proxyAdmin: Contract) { - try { - console.log(chalk.gray(`ProxyAdmin version ${ - // This function name is set in external library - // eslint-disable-next-line new-cap - await proxyAdmin.UPGRADE_INTERFACE_VERSION() - }`)); - return true; - } catch (error) { - console.log(chalk.gray("Use old ProxyAdmin")); - return false; - } - } } From 83ecadedc399bc96935cc060405869e52e975e21 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 6 Sep 2024 16:37:17 +0300 Subject: [PATCH 2/2] Change access --- src/upgrader.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/upgrader.ts b/src/upgrader.ts index b244fcc..6f37e23 100644 --- a/src/upgrader.ts +++ b/src/upgrader.ts @@ -26,15 +26,16 @@ const deployTimeout = 60e4; export abstract class Upgrader { - private instance: Instance; private targetVersion: string; private contractNamesToUpgrade: string[]; private projectName: string; - private transactions: Transaction[]; private submitter: Submitter; private nonceProvider?: NonceProvider; private deploySemaphore: Semaphore; + protected instance: Instance; + protected transactions: Transaction[]; + constructor ( project: Project, submitter?: Submitter