Skip to content

Commit

Permalink
Structure upgradable verification functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
FilipTxFusion committed Jun 1, 2023
1 parent 881facf commit a730d07
Show file tree
Hide file tree
Showing 21 changed files with 282 additions and 518 deletions.
5 changes: 5 additions & 0 deletions .changeset/clean-falcons-know.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@matterlabs/hardhat-zksync-verify": patch
---

Enable passing encoded constructor arguments in 'verify:verify' task
4 changes: 2 additions & 2 deletions packages/hardhat-zksync-upgradable/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@matterlabs/hardhat-zksync-upgradable",
"version": "0.0.1-alpha.3",
"version": "0.0.1-alpha.4",
"description": "Hardhat plugin to deploy and update upgradable smart contracts for the zkSync network",
"repository": "github:matter-labs/hardhat-zksync",
"homepage": "https://github.com/matter-labs/hardhat-zksync/tree/main/packages/hardhat-zksync-upgradable",
Expand Down Expand Up @@ -32,7 +32,7 @@
"README.md"
],
"dependencies": {
"@matterlabs/hardhat-zksync-deploy": "^0.6.2",
"@matterlabs/hardhat-zksync-deploy": "^0.6.3",
"@matterlabs/hardhat-zksync-solc": "^0.3.16",
"dockerode": "^3.3.4"
},
Expand Down
5 changes: 3 additions & 2 deletions packages/hardhat-zksync-upgradable/src/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Contract } from 'ethers';
import { Manifest } from './core/manifest';
import { Wallet } from 'zksync-web3';
import { getAdminFactory } from './proxy-deployment/deploy-proxy-admin';
import { ZkSyncUpgradablePluginError } from './errors';

export type ChangeAdminFunction = (proxyAddress: string, newAdmin: string, wallet: Wallet) => Promise<void>;
export type TransferProxyAdminOwnershipFunction = (newOwner: string, wallet: Wallet) => Promise<void>;
Expand All @@ -17,7 +18,7 @@ export function makeChangeProxyAdmin(hre: HardhatRuntimeEnvironment): ChangeAdmi
const proxyAdminAddress = await getAdminAddress(wallet.provider, proxyAddress);

if (proxyAdminManifest.address !== proxyAdminAddress) {
throw new Error('Proxy admin is not the one registered in the network manifest');
throw new ZkSyncUpgradablePluginError('Proxy admin is not the one registered in the network manifest');
} else if (proxyAdminManifest.address !== newAdmin) {
await proxyAdminManifest.changeProxyAdmin(proxyAddress, newAdmin);
}
Expand Down Expand Up @@ -54,7 +55,7 @@ export async function getManifestAdmin(hre: HardhatRuntimeEnvironment, wallet: W
const proxyAdminAddress = manifestAdmin?.address;

if (proxyAdminAddress === undefined) {
throw new Error('No ProxyAdmin was found in the network manifest');
throw new ZkSyncUpgradablePluginError('No ProxyAdmin was found in the network manifest');
}

const adminFactory = await getAdminFactory(hre, wallet);
Expand Down
15 changes: 13 additions & 2 deletions packages/hardhat-zksync-upgradable/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const PLUGIN_NAME = '@matterlabs/hardhat-zksync-upgradable';

export const ITUP_JSON = '/ITransparentUpgradeableProxy.json';
export const TUP_JSON = '/TransparentUpgradeableProxy.json';
export const BEACON_PROXY_JSON = '/BeaconProxy.json';
Expand All @@ -16,5 +18,14 @@ export const PROXY_SOURCE_NAMES = [
'@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol',
];

export const UPGRADE_VERIFY_ERROR = 'The hardhat-etherscan plugin must be imported before the hardhat-upgrades plugin.' +
'Import the plugins in the following order in hardhat.config.js:\n'
export const UPGRADE_VERIFY_ERROR =
'The verify plugin must be imported before the hardhat-upgrades plugin.' +
'Import the plugins in the following order in hardhat.config.js:\n';

export const verifiableContracts = {
erc1967proxy: { event: 'Upgraded(address)' },
beaconProxy: { event: 'BeaconUpgraded(address)' },
upgradeableBeacon: { event: 'OwnershipTransferred(address,address)' },
transparentUpgradeableProxy: { event: 'AdminChanged(address,address)' },
proxyAdmin: { event: 'OwnershipTransferred(address,address)' },
};
20 changes: 11 additions & 9 deletions packages/hardhat-zksync-upgradable/src/core/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { mapValues, pick } from '../utils/utils-general';
import * as zk from 'zksync-web3';
import { getChainId, networkNames } from './provider';
import { MANIFEST_DEFAULT_DIR } from '../constants';
import { ZkSyncUpgradablePluginError } from '../errors';

const currentManifestVersion = '3.2';

Expand All @@ -30,6 +31,8 @@ export interface ProxyDeployment extends Deployment {
kind: 'uups' | 'transparent' | 'beacon';
}

export class DeploymentNotFound extends ZkSyncUpgradablePluginError {}

function defaultManifest(): ManifestData {
return {
manifestVersion: currentManifestVersion,
Expand Down Expand Up @@ -129,15 +132,15 @@ export class Manifest {

async write(data: ManifestData): Promise<void> {
if (!this.locked) {
throw new Error('Manifest must be locked');
throw new ZkSyncUpgradablePluginError('Manifest must be locked');
}
const normalized = normalizeManifestData(data);
await this.writeFile(JSON.stringify(normalized, null, 2) + '\n');
}

async lockedRun<T>(cb: () => Promise<T>): Promise<T> {
if (this.locked) {
throw new Error('Manifest is already locked');
throw new ZkSyncUpgradablePluginError('Manifest is already locked');
}
const release = await this.lock();
try {
Expand All @@ -162,15 +165,17 @@ export class Manifest {

function validateOrUpdateManifestVersion(data: ManifestData): ManifestData {
if (typeof data.manifestVersion !== 'string') {
throw new Error('Manifest version is missing');
throw new ZkSyncUpgradablePluginError('Manifest version is missing');
} else if (compareVersions(data.manifestVersion, '3.0', '<')) {
throw new Error('Found a manifest file for OpenZeppelin CLI. An automated migration is not yet available.');
throw new ZkSyncUpgradablePluginError(
'Found a manifest file for OpenZeppelin CLI. An automated migration is not yet available.'
);
} else if (compareVersions(data.manifestVersion, currentManifestVersion, '<')) {
return migrateManifest(data);
} else if (data.manifestVersion === currentManifestVersion) {
return data;
} else {
throw new Error(`Unknown value for manifest version (${data.manifestVersion})`);
throw new ZkSyncUpgradablePluginError(`Unknown value for manifest version (${data.manifestVersion})`);
}
}

Expand All @@ -182,12 +187,9 @@ export function migrateManifest(data: ManifestData): ManifestData {
data.proxies = [];
return data;
default:
throw new Error('Manifest migration not available');
throw new ZkSyncUpgradablePluginError('Manifest migration not available');
}
}

export class DeploymentNotFound extends Error {}

export function normalizeManifestData(input: ManifestData): ManifestData {
return {
manifestVersion: input.manifestVersion,
Expand Down
8 changes: 5 additions & 3 deletions packages/hardhat-zksync-upgradable/src/core/proxy-kind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import {
isTransparentOrUUPSProxy,
isTransparentProxy,
BeaconProxyUnsupportedError,
UpgradesError,
Version,
} from '@openzeppelin/upgrades-core';
import { Manifest, DeploymentNotFound, ProxyDeployment } from './manifest';
import * as zk from 'zksync-web3';
import { ZkSyncUpgradablePluginError } from '../errors';

export async function setProxyKind(
provider: zk.Provider,
Expand All @@ -30,7 +30,9 @@ export async function setProxyKind(
if (opts.kind === undefined) {
opts.kind = manifestDeployment?.kind ?? 'transparent';
} else if (manifestDeployment && opts.kind !== manifestDeployment.kind) {
throw new Error(`Requested an upgrade of kind ${opts.kind} but proxy is ${manifestDeployment.kind}`);
throw new ZkSyncUpgradablePluginError(
`Requested an upgrade of kind ${opts.kind} but proxy is ${manifestDeployment.kind}`
);
}

return opts.kind;
Expand Down Expand Up @@ -68,7 +70,7 @@ export async function detectProxyKind(provider: zk.Provider, proxyAddress: strin
} else if (await isBeaconProxy(provider, proxyAddress)) {
importKind = 'beacon';
} else {
throw new UpgradesError(`Contract at ${proxyAddress} doesn't look like an ERC 1967 proxy`);
throw new ZkSyncUpgradablePluginError(`Contract at ${proxyAddress} doesn't look like an ERC 1967 proxy`);
}
return importKind;
}
8 changes: 8 additions & 0 deletions packages/hardhat-zksync-upgradable/src/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { HardhatPluginError } from 'hardhat/plugins';
import { PLUGIN_NAME } from './constants';

export class ZkSyncUpgradablePluginError extends HardhatPluginError {
constructor(message: string, parentError?: Error) {
super(PLUGIN_NAME, message, parentError);
}
}
3 changes: 1 addition & 2 deletions packages/hardhat-zksync-upgradable/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@ subtask(TASK_COMPILE_SOLIDITY_GET_SOURCE_NAMES, async (args: RunCompilerArgs, _,
return [...sourceNames, ...PROXY_SOURCE_NAMES];
});


subtask('verify:verify').setAction(async (args, hre, runSuper) => {
const { verify } = await import('./verify/verify-proxy');
return await verify(args, hre, runSuper);
});
});
4 changes: 4 additions & 0 deletions packages/hardhat-zksync-upgradable/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,7 @@ export type ContractAddressOrInstance = string | { address: string };
export type RecursivePartial<T> = { [k in keyof T]?: RecursivePartial<T[k]> };

export type MaybeSolcOutput = RecursivePartial<SolcOutput>;

export interface VerifiableContractInfo {
event: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
isBeacon,
DeployBeaconProxyUnsupportedError,
DeployBeaconProxyKindError,
UpgradesError,
} from '@openzeppelin/upgrades-core';

import * as zk from 'zksync-web3';
Expand All @@ -16,7 +15,8 @@ import { ContractAddressOrInstance, getContractAddress } from '../utils/utils-ge
import { DeployBeaconProxyOptions } from '../utils/options';
import { getInitializerData } from '../utils/utils-general';
import { deploy, DeployTransaction } from './deploy';
import { BEACON_PROXY_JSON, TUP_JSON } from '../constants';
import { BEACON_PROXY_JSON } from '../constants';
import { ZkSyncUpgradablePluginError } from '../errors';
import { Manifest } from '../core/manifest';
import chalk from 'chalk';
import assert from 'assert';
Expand Down Expand Up @@ -48,9 +48,9 @@ export function makeDeployBeaconProxy(hre: HardhatRuntimeEnvironment): DeployBea
const attachTo = new zk.ContractFactory(artifact.abi, artifact.bytecode, wallet);

if (!(attachTo instanceof zk.ContractFactory)) {
throw new UpgradesError(
`attachTo must specify a contract factory`,
() => `Include the contract factory for the beacon's current implementation in the attachTo parameter`
throw new ZkSyncUpgradablePluginError(
`attachTo must specify a contract factory\n` +
`Include the contract factory for the beacon's current implementation in the attachTo parameter`
);
}
if (!Array.isArray(args)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { deploy } from './deploy';
import { fetchOrDeployGetDeployment } from '../core/impl-store';
import { TransactionResponse } from 'zksync-web3/src/types';
import { FORMAT_TYPE_MINIMAL } from '../constants';
import { ZkSyncUpgradablePluginError } from '../errors';

export interface DeployData {
provider: zk.Provider;
Expand Down Expand Up @@ -70,9 +71,8 @@ async function deployImpl(
const abi = factory.interface.format(FORMAT_TYPE_MINIMAL) as string[];
const attemptDeploy = async () => {
if (opts.useDeployedImplementation) {
throw new UpgradesError(
'The implementation contract was not previously deployed.',
() =>
throw new ZkSyncUpgradablePluginError(
'The implementation contract was not previously deployed.\n' +
'The useDeployedImplementation option was set to true but the implementation contract was not previously deployed on this network.'
);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { getInitializerData } from '../utils/utils-general';
import { ERC1967_PROXY_JSON, TUP_JSON } from '../constants';
import { Manifest, ProxyDeployment } from '../core/manifest';
import { DeployProxyOptions } from '../utils/options';
import { ZkSyncUpgradablePluginError } from '../errors';
import assert from 'assert';

export interface DeployFunction {
Expand Down Expand Up @@ -64,6 +65,7 @@ export function makeDeployProxy(hre: HardhatRuntimeEnvironment): DeployFunction
const proxyContract = await import(ERC1967ProxyPath);
const proxyFactory = new zk.ContractFactory(proxyContract.abi, proxyContract.bytecode, wallet);
proxyDeployment = Object.assign({ kind }, await deploy(proxyFactory, impl, data));
console.info(chalk.green(`UUPS proxy was deployed to ${proxyDeployment.address}`));
break;
}

Expand All @@ -83,7 +85,7 @@ export function makeDeployProxy(hre: HardhatRuntimeEnvironment): DeployFunction
}

default: {
throw new Error(`Unknown proxy kind: ${kind}`);
throw new ZkSyncUpgradablePluginError(`Unknown proxy kind: ${kind}`);
}
}

Expand Down
71 changes: 39 additions & 32 deletions packages/hardhat-zksync-upgradable/src/utils/utils-general.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { Interface } from '@ethersproject/abi';
import { MaybeSolcOutput } from '../interfaces';
import { ZkSyncUpgradablePluginError } from '../errors';
import { keccak256 } from 'ethereumjs-util';
import { Interface } from '@ethersproject/abi';
import chalk from 'chalk';
import axios from 'axios';
import * as zk from 'zksync-web3';

export type ContractAddressOrInstance = string | { address: string };

Expand Down Expand Up @@ -38,7 +41,6 @@ export function getInitializerData(
}
}


/**
* Gets the constructor args from the given transaction input and creation code.
*
Expand All @@ -48,52 +50,57 @@ export function getInitializerData(
*/
export function inferConstructorArgs(txInput: string, creationCode: string) {
if (txInput.startsWith(creationCode)) {
return txInput.substring(creationCode.length);
return txInput.substring(creationCode.length);
} else {
return undefined;
return undefined;
}
}
}

/**
* Gets the txhash that created the contract at the given address, by calling the
* Etherscan API to look for an event that should have been emitted during construction.
*
* @param address The address to get the creation txhash for.
* @param topic The event topic string that should have been logged.
* @param etherscanApi The Etherscan API config
* @returns The txhash corresponding to the logged event, or undefined if not found or if
* the address is not a contract.
* @throws {UpgradesError} if the Etherscan API returned with not OK status
*/
export async function getContractCreationTxHash(
address: string,
topic: string,
): Promise<any> {
export async function getContractCreationTxHash(provider: zk.Provider, address: string, topic: string): Promise<any> {
const params = {
module: 'logs',
action: 'getLogs',
fromBlock: '0',
toBlock: 'latest',
address: address,
topic0: '0x' + keccak256(Buffer.from(topic)).toString('hex'),
fromBlock: 0,
toBlock: 'latest',
address: address,
topics: ['0x' + keccak256(Buffer.from(topic)).toString('hex')],
};

const responseBody = {status: 200, message: 'OK', result:[ {transactionHash: '0x1234567890123456789012345678901234567890123456789012345678901234'}]};

//FIXME: replace with a proper check for the status code
if (responseBody.status === 200) {
const result = responseBody.result;
return result[0].transactionHash; // get the txhash from the first instance of this event
} else if (responseBody.message === 'No records found' || responseBody.message === 'No logs found') {
console.info(chalk.yellow(`no result found for event topic ${topic} at address ${address}`));
return undefined;

const logs = await provider.getLogs(params);

if (logs.length > 0) {
return logs[0].transactionHash; // get the txhash from the first instance of this event
} else {
throw new Error(
`Failed to get logs for contract at address ${address}.`+
`Etherscan returned with message: ${responseBody.message}, reason: ${responseBody.result}`,
);
console.warn(
chalk.yellow(
`No logs found for event topic ${topic} at address ${address}\n` +
`One of possible reasons can be that you are trying to verify UUPS contract`
)
);
}
}
}

export async function callEtherscanApi(url: string, params: any): Promise<any> {
const response = await axios.post(url, params, { headers: { 'Content-Type': 'application/json' } });

if (!(response.status >= 200 && response.status <= 299)) {
const responseBodyText = await response.data;
throw new ZkSyncUpgradablePluginError(
`Block explorer API call failed with status ${response.status}, response: ${responseBodyText}`
);
}

const responseBodyJson = await response.data;

return responseBodyJson;
}

export function pick<T, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
const res: Partial<Pick<T, K>> = {};
Expand Down
Loading

0 comments on commit a730d07

Please sign in to comment.