diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 0dd45ed..d34b3ed 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -27,7 +27,6 @@ module.exports = { "never" ], - "max-statements": "warn", "multiline-comment-style": "warn", "no-await-in-loop": "warn", "no-console": "warn", diff --git a/src/contractFactory.ts b/src/contractFactory.ts new file mode 100644 index 0000000..1ac3030 --- /dev/null +++ b/src/contractFactory.ts @@ -0,0 +1,109 @@ +import {artifacts, ethers} from "hardhat"; +import {promises as fs} from "fs"; +import {hashBytecode} from "@openzeppelin/upgrades-core"; +import {LinkReferences} from "hardhat/types"; +import {SkaleManifestData} from "./types/SkaleManifestData"; +import { + deployLibraries, + getLinkedContractFactory, + getManifestFile +} from "./deploy"; + + +const getSkaleManifest = async () => { + const manifest = JSON.parse(await fs.readFile( + await getManifestFile(), + "utf-8" + )); + if (manifest.libraries === undefined) { + manifest.libraries = {}; + } + return manifest as SkaleManifestData; +}; + +const updateManifest = async ( + manifest: SkaleManifestData, + libraries: Map, + oldLibraries: {[k: string]: string} +) => { + for (const [ + libraryName, + libraryAddress + ] of libraries.entries()) { + const {bytecode} = await artifacts.readArtifact(libraryName); + manifest.libraries[libraryName] = { + "address": libraryAddress, + "bytecodeHash": hashBytecode(bytecode) + }; + } + Object.assign( + libraries, + oldLibraries + ); + await fs.writeFile( + await getManifestFile(), + JSON.stringify( + manifest, + null, + 4 + ) + ); +}; + +export const getContractFactoryAndUpdateManifest = async (contract: string) => { + const {linkReferences} = await artifacts.readArtifact(contract); + if (!Object.keys(linkReferences).length) { + return await ethers.getContractFactory(contract); + } + + const manifest = await getSkaleManifest(); + + const { + librariesToUpgrade, + oldLibraries + } = await getLibrariesToUpgrade( + manifest, + linkReferences + ); + const libraries = await deployLibraries(librariesToUpgrade); + await updateManifest( + manifest, + libraries, + oldLibraries + ); + return await getLinkedContractFactory( + contract, + libraries + ); +}; + +const getLibrariesNames = + (linkReferences: LinkReferences) => Object.values(linkReferences). + map((libraryObject) => Object.keys(libraryObject)[0]); + + +const getLibrariesToUpgrade = async ( + manifest: SkaleManifestData, + linkReferences: LinkReferences +) => { + const librariesToUpgrade = []; + const oldLibraries: {[k: string]: string} = {}; + for (const libraryName of getLibrariesNames(linkReferences)) { + const {bytecode} = await artifacts.readArtifact(libraryName); + if (manifest.libraries[libraryName] === undefined) { + librariesToUpgrade.push(libraryName); + } else if ( + hashBytecode(bytecode) !== manifest.libraries[libraryName]. + bytecodeHash + ) { + librariesToUpgrade.push(libraryName); + } else { + oldLibraries[libraryName] = + manifest.libraries[libraryName].address; + } + } + return { + librariesToUpgrade, + oldLibraries + }; +}; diff --git a/src/deploy.ts b/src/deploy.ts index da3d25a..f4df96d 100644 --- a/src/deploy.ts +++ b/src/deploy.ts @@ -2,7 +2,7 @@ import {Manifest, hashBytecode} from "@openzeppelin/upgrades-core"; import {artifacts, ethers} from "hardhat"; import {promises as fs} from "fs"; import {SkaleManifestData} from "./types/SkaleManifestData"; -import {Artifact} from "hardhat/types"; +import {Artifact, LinkReferences} from "hardhat/types"; interface LibraryArtifacts { [key: string]: unknown @@ -99,19 +99,7 @@ const updateManifest = async (libraryArtifacts: LibraryArtifacts) => { ); }; -export const getContractFactory = async (contract: string) => { - const {linkReferences} = await artifacts.readArtifact(contract); - if (!Object.keys(linkReferences).length) { - return await ethers.getContractFactory(contract); - } - - const libraryNames = []; - for (const key of Object.keys(linkReferences)) { - const libraryName = Object.keys(linkReferences[key])[0]; - libraryNames.push(libraryName); - } - - const libraries = await deployLibraries(libraryNames); +const getLibraryArtifacts = async (libraries: Map) => { const libraryArtifacts: LibraryArtifacts = {}; for (const [ libraryName, @@ -123,6 +111,27 @@ export const getContractFactory = async (contract: string) => { "bytecodeHash": hashBytecode(bytecode) }; } + return libraryArtifacts; +}; + +const getLibraryNames = (linkReferences: LinkReferences) => { + const libraryNames = []; + for (const key of Object.keys(linkReferences)) { + const libraryName = Object.keys(linkReferences[key])[0]; + libraryNames.push(libraryName); + } + return libraryNames; +}; + +export const getContractFactory = async (contract: string) => { + const {linkReferences} = await artifacts.readArtifact(contract); + if (!Object.keys(linkReferences).length) { + return await ethers.getContractFactory(contract); + } + + const libraryNames = getLibraryNames(linkReferences); + const libraries = await deployLibraries(libraryNames); + const libraryArtifacts = await getLibraryArtifacts(libraries); await updateManifest(libraryArtifacts); diff --git a/src/types/SkaleManifestData.ts b/src/types/SkaleManifestData.ts index 1fb82ed..caabeff 100644 --- a/src/types/SkaleManifestData.ts +++ b/src/types/SkaleManifestData.ts @@ -1,7 +1,7 @@ import {ManifestData} from "@openzeppelin/upgrades-core"; export interface SkaleManifestData extends ManifestData { - libraries?: { + libraries: { [libraryName: string]: { address: string bytecodeHash: string diff --git a/src/upgrader.ts b/src/upgrader.ts index 6921f69..09f8609 100644 --- a/src/upgrader.ts +++ b/src/upgrader.ts @@ -74,31 +74,54 @@ export abstract class Upgrader { // Public async upgrade () { - const proxyAdmin = await getManifestAdmin(hre) as unknown as ProxyAdmin; + const version = await this.prepareVersion(); - const version = await getVersion(); - await this.checkVersion(version); - console.log(`Will mark updated version as ${version}`); - - if (this.deployNewContracts !== undefined) { - // Deploy new contracts - await this.deployNewContracts(); - } + await this.callDeployNewContracts(); const contractsToUpgrade = await this.deployNewImplementations(); this.switchToNewImplementations( contractsToUpgrade, - proxyAdmin + await getManifestAdmin(hre) as unknown as ProxyAdmin ); + await this.callInitialize(); + + // Write version + await this.setVersion(version); + + await this.writeTransactions(version); + + await this.submitter.submit(this.transactions); + + await Upgrader.verify(contractsToUpgrade); + + console.log("Done"); + } + + // Private + + private async callInitialize () { if (this.initialize !== undefined) { await this.initialize(); } + } - // Write version - await this.setVersion(version); + private async callDeployNewContracts () { + if (this.deployNewContracts !== undefined) { + // Deploy new contracts + await this.deployNewContracts(); + } + } + private async prepareVersion () { + const version = await getVersion(); + await this.checkVersion(version); + console.log(`Will mark updated version as ${version}`); + return version; + } + + private async writeTransactions (version: string) { await fs.writeFile( `data/transactions-${version}-${network.name}.json`, JSON.stringify( @@ -107,16 +130,8 @@ export abstract class Upgrader { 4 ) ); - - await this.submitter.submit(this.transactions); - - await Upgrader.verify(contractsToUpgrade); - - console.log("Done"); } - // Private - private static async verify (contractsToUpgrade: ContractToUpgrade[]) { if (process.env.NO_VERIFY) { console.log("Skip verification"); @@ -158,36 +173,44 @@ export abstract class Upgrader { private async deployNewImplementations () { const contractsToUpgrade: ContractToUpgrade[] = []; for (const contract of this.contractNamesToUpgrade) { - const contractFactory = + const updatedContract = + await this.deployNewImplementation(contract); + if (updatedContract !== undefined) { + contractsToUpgrade.push(updatedContract); + } + } + return contractsToUpgrade; + } + + private async deployNewImplementation (contract: string) { + const contractFactory = await getContractFactoryAndUpdateManifest(contract); - const proxyAddress = + const proxyAddress = (await this.instance.getContract(contract)).address; - console.log(`Prepare upgrade of ${contract}`); - const - currentImplementationAddress = await getImplementationAddress( - network.provider, - proxyAddress - ); - const newImplementationAddress = await upgrades.prepareUpgrade( - proxyAddress, - contractFactory, - { - "unsafeAllowLinkedLibraries": true, - "unsafeAllowRenames": true - } - ) as string; - if (newImplementationAddress !== currentImplementationAddress) { - contractsToUpgrade.push({ - proxyAddress, - "implementationAddress": newImplementationAddress, - "name": contract - }); - } else { - console.log(chalk.gray(`Contract ${contract} is up to date`)); + console.log(`Prepare upgrade of ${contract}`); + const + currentImplementationAddress = await getImplementationAddress( + network.provider, + proxyAddress + ); + const newImplementationAddress = await upgrades.prepareUpgrade( + proxyAddress, + contractFactory, + { + "unsafeAllowLinkedLibraries": true, + "unsafeAllowRenames": true } + ) as string; + if (newImplementationAddress !== currentImplementationAddress) { + return { + proxyAddress, + "implementationAddress": newImplementationAddress, + "name": contract + }; } - return contractsToUpgrade; + console.log(chalk.gray(`Contract ${contract} is up to date`)); + return undefined; } private async getNormalizedDeployedVersion () { diff --git a/src/verification.ts b/src/verification.ts index d2ddb6d..d036d55 100644 --- a/src/verification.ts +++ b/src/verification.ts @@ -5,6 +5,50 @@ import { import chalk from "chalk"; import {getImplementationAddress} from "@openzeppelin/upgrades-core"; +const processError = (error: unknown, contractName: string) => { + if (error instanceof Error) { + const alreadyVerifiedErrorLine = + "Contract source code already verified"; + if (error.toString().includes(alreadyVerifiedErrorLine)) { + const infoMessage = `${contractName} is already verified`; + console.log(chalk.grey(infoMessage)); + return; + } + const errorMessage = + `Contract ${contractName} was not verified on etherscan`; + console.log(chalk.red(errorMessage)); + console.log(error.toString()); + } else { + console.log( + "Unknown exception type:", + error + ); + } +}; + +const verificationAttempt = async ( + contractName: string, + contractAddress: string, + constructorArguments: object +) => { + try { + await run( + "verify:verify", + { + "address": contractAddress, + constructorArguments + } + ); + return true; + } catch (error) { + processError( + error, + contractName + ); + } + return false; +}; + export const verify = async ( contractName: string, contractAddress: string, @@ -17,34 +61,12 @@ export const verify = async ( return; } for (let retry = 0; retry <= 5; retry += 1) { - try { - await run( - "verify:verify", - { - "address": contractAddress, - constructorArguments - } - ); + if (await verificationAttempt( + contractName, + contractAddress, + constructorArguments + )) { break; - } catch (error) { - if (error instanceof Error) { - const alreadyVerifiedErrorLine = - "Contract source code already verified"; - if (error.toString().includes(alreadyVerifiedErrorLine)) { - const infoMessage = `${contractName} is already verified`; - console.log(chalk.grey(infoMessage)); - return; - } - const errorMessage = - `Contract ${contractName} was not verified on etherscan`; - console.log(chalk.red(errorMessage)); - console.log(error.toString()); - } else { - console.log( - "Unknown exception type:", - error - ); - } } } };