Skip to content

Commit

Permalink
Merge pull request #298 from skalenetwork/openzeppelin-5
Browse files Browse the repository at this point in the history
Add support of openzeppelin v5 transparent proxy
  • Loading branch information
DimaStebaev authored Sep 9, 2024
2 parents 7d230a2 + 83ecade commit 8039d58
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 79 deletions.
61 changes: 61 additions & 0 deletions src/proxyAdmin.ts
Original file line number Diff line number Diff line change
@@ -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)
});
}
16 changes: 11 additions & 5 deletions src/submitters/auto-submitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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 = [
{
Expand All @@ -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();
Expand Down
120 changes: 46 additions & 74 deletions src/upgrader.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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";

Expand All @@ -25,18 +26,19 @@ const deployTimeout = 60e4;


export abstract class Upgrader {
instance: Instance;
targetVersion: string;
contractNamesToUpgrade: string[];
projectName: string;
transactions: Transaction[];
submitter: Submitter;
nonceProvider?: NonceProvider;
deploySemaphore: Semaphore;
private targetVersion: string;
private contractNamesToUpgrade: string[];
private projectName: string;
private submitter: Submitter;
private nonceProvider?: NonceProvider;
private deploySemaphore: Semaphore;

protected instance: Instance;
protected transactions: Transaction[];

constructor (
project: Project,
submitter: Submitter = new AutoSubmitter()
submitter?: Submitter
) {
this.targetVersion = project.version;
if (!project.version.includes("-")) {
Expand All @@ -46,7 +48,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);
}

Expand All @@ -69,8 +71,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
Expand All @@ -81,23 +82,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<string>
)
);
return owners.reduce( (owner1, owner2) => {
if (owner1 !== owner2) {
throw Error("Proxies have different owners");
}
return owner1;
})
}

// Private
Expand Down Expand Up @@ -148,42 +155,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 () {
Expand Down Expand Up @@ -280,18 +266,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;
}
}
}

0 comments on commit 8039d58

Please sign in to comment.