@openzeppelin/hardhat-upgrades
Version:
[](https://docs.openzeppelin.com/upgrades-plugins/hardhat-upgrades) [](https://www.npmjs.org/package/@openzeppelin/h
340 lines • 17.1 kB
JavaScript
import { getTransactionByHash, getImplementationAddress, getBeaconAddress, getImplementationAddressFromBeacon, UpgradesError, getAdminAddress, isTransparentOrUUPSProxy, isBeacon, isBeaconProxy, isEmptySlot, getCode, } from '@openzeppelin/upgrades-core';
import artifactsBuildInfo from '@openzeppelin/upgrades-core/artifacts/build-info-v5.json';
import ERC1967Proxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json';
import BeaconProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol/BeaconProxy.json';
import UpgradeableBeacon from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json';
import TransparentUpgradeableProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json';
import ProxyAdmin from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json';
import { keccak256 } from 'ethereumjs-util';
import debug from './utils/debug.js';
import { callEtherscanApi, getEtherscanFromConnection, RESPONSE_OK, verifyAndGetStatus, } from './utils/etherscan-api.js';
const verifiableContracts = {
erc1967proxy: { artifact: ERC1967Proxy, event: 'Upgraded(address)' },
beaconProxy: { artifact: BeaconProxy, event: 'BeaconUpgraded(address)' },
upgradeableBeacon: {
artifact: UpgradeableBeacon,
event: 'OwnershipTransferred(address,address)',
},
transparentUpgradeableProxy: {
artifact: TransparentUpgradeableProxy,
event: 'AdminChanged(address,address)',
},
proxyAdmin: { artifact: ProxyAdmin, event: 'OwnershipTransferred(address,address)' },
};
/**
* Overrides hardhat-verify's verify flow to fully verify a proxy or beacon.
* Verifies the contract at an address. If the address is an ERC-1967 compatible proxy, verifies the proxy and associated proxy contracts,
* as well as the implementation. Otherwise, calls hardhat-verify's verify function directly.
*
* Requires @nomicfoundation/hardhat-verify v3.0.10+ and uses verification.etherscan from hre.network.getOrCreate().
*/
export async function verify(args, hre, runSuper) {
const connection = await hre.network.getOrCreate();
const { provider } = connection;
const proxyAddress = args.address;
const errorReport = {
errors: [],
severity: 'error',
};
let proxy = true;
async function hardhatVerify(address) {
return await runSuper({ ...args, address });
}
if (await isTransparentOrUUPSProxy(provider, proxyAddress)) {
await fullVerifyTransparentOrUUPS(hre, proxyAddress, hardhatVerify, errorReport);
}
else if (await isBeaconProxy(provider, proxyAddress)) {
await fullVerifyBeaconProxy(hre, proxyAddress, hardhatVerify, errorReport);
}
else if (await isBeacon(provider, proxyAddress)) {
proxy = false;
const etherscan = await getEtherscanFromConnection(hre);
await fullVerifyBeacon(hre, proxyAddress, hardhatVerify, etherscan, errorReport);
}
else {
return await hardhatVerify(proxyAddress);
}
if (errorReport.errors.length > 0) {
displayErrorReport(errorReport);
}
else {
console.info(`\n${proxy ? 'Proxy' : 'Contract'} fully verified.`);
}
return undefined;
}
function displayErrorReport(errorReport) {
let summary = `\nVerification completed with the following ${errorReport.severity === 'error' ? 'errors' : 'warnings'}.`;
for (let i = 0; i < errorReport.errors.length; i++) {
summary += `\n\n${errorReport.severity === 'error' ? 'Error' : 'Warning'} ${i + 1}: ${errorReport.errors[i]}`;
}
if (errorReport.severity === 'error') {
throw new UpgradesError(summary);
}
else {
console.warn(summary);
}
}
function recordVerificationError(address, contractType, details, errorReport) {
const message = `Failed to verify ${contractType} contract at ${address}: ${details}`;
recordError(message, errorReport);
}
function recordError(message, errorReport) {
console.error(message);
errorReport.errors.push(message);
}
class EventOrFunctionNotFound extends UpgradesError {
}
class EventsNotFound extends EventOrFunctionNotFound {
constructor(address, events) {
super(`Could not find an event with any of the following topics in the logs for address ${address}: ${events.join(', ')}`, () => 'If the proxy was recently deployed, the transaction may not be available on Etherscan yet. Try running the verify task again after waiting a few blocks.');
}
}
class BytecodeNotMatchArtifact extends Error {
contractName;
constructor(message, contractName) {
super(message);
this.contractName = contractName;
}
}
async function fullVerifyTransparentOrUUPS(hre, proxyAddress, hardhatVerify, errorReport) {
const connection = await hre.network.getOrCreate();
const implAddress = await getImplementationAddress(connection.provider, proxyAddress);
await verifyImplementation(hardhatVerify, implAddress, errorReport);
const etherscan = await getEtherscanFromConnection(hre);
await verifyTransparentOrUUPS();
await linkProxyWithImplementationAbi(etherscan, proxyAddress, implAddress, errorReport);
await verifyAdmin();
async function verifyAdmin() {
const adminAddress = await getAdminAddress(connection.provider, proxyAddress);
if (!isEmptySlot(adminAddress)) {
console.log(`Verifying proxy admin: ${adminAddress}`);
await verifyAdminOrFallback(hre, hardhatVerify, etherscan, adminAddress, errorReport);
}
}
async function verifyTransparentOrUUPS() {
console.log(`Verifying proxy: ${proxyAddress}`);
await verifyWithArtifactOrFallback(hre, hardhatVerify, etherscan, proxyAddress, [verifiableContracts.transparentUpgradeableProxy, verifiableContracts.erc1967proxy], errorReport, true);
}
}
async function verifyAdminOrFallback(hre, hardhatVerify, etherscan, adminAddress, errorReport) {
const attemptVerify = async () => {
let encodedOwner;
const response = await getEventResponse(adminAddress, verifiableContracts.proxyAdmin.event, etherscan);
if (response === undefined) {
throw new EventsNotFound(adminAddress, [verifiableContracts.proxyAdmin.event]);
}
else if (response.topics.length !== 3) {
throw new EventOrFunctionNotFound(`Unexpected number of topics in event logs for ${verifiableContracts.proxyAdmin.event} from ${adminAddress}. Expected 3, got ${response.topics.length}: ${response.topics}`, () => `The contract at ${adminAddress} does not appear to be a known proxy admin contract.`);
}
else {
encodedOwner = response.topics[2].replace(/^0x/, '');
}
const artifact = verifiableContracts.proxyAdmin.artifact;
const connection = await hre.network.getOrCreate();
const deployedBytecode = await getCode(connection.provider, adminAddress);
if (deployedBytecode !== artifact.deployedBytecode) {
throw new BytecodeNotMatchArtifact(`Bytecode does not match with the current version of ${artifact.contractName} in the Hardhat Upgrades plugin.`, artifact.contractName);
}
await verifyContractWithConstructorArgs(etherscan, adminAddress, artifact, encodedOwner, errorReport);
};
await attemptVerifyOrFallback(attemptVerify, hardhatVerify, adminAddress, errorReport, false);
}
async function fullVerifyBeaconProxy(hre, proxyAddress, hardhatVerify, errorReport) {
const connection = await hre.network.getOrCreate();
const beaconAddress = await getBeaconAddress(connection.provider, proxyAddress);
const implAddress = await getImplementationAddressFromBeacon(connection.provider, beaconAddress);
const etherscan = await getEtherscanFromConnection(hre);
await fullVerifyBeacon(hre, beaconAddress, hardhatVerify, etherscan, errorReport);
await verifyBeaconProxy();
await linkProxyWithImplementationAbi(etherscan, proxyAddress, implAddress, errorReport);
async function verifyBeaconProxy() {
console.log(`Verifying beacon proxy: ${proxyAddress}`);
await verifyWithArtifactOrFallback(hre, hardhatVerify, etherscan, proxyAddress, [verifiableContracts.beaconProxy], errorReport, true);
}
}
async function fullVerifyBeacon(hre, beaconAddress, hardhatVerify, etherscan, errorReport) {
const connection = await hre.network.getOrCreate();
const implAddress = await getImplementationAddressFromBeacon(connection.provider, beaconAddress);
await verifyImplementation(hardhatVerify, implAddress, errorReport);
await verifyBeacon();
async function verifyBeacon() {
console.log(`Verifying beacon or beacon-like contract: ${beaconAddress}`);
await verifyWithArtifactOrFallback(hre, hardhatVerify, etherscan, beaconAddress, [verifiableContracts.upgradeableBeacon], errorReport, true);
}
}
async function verifyImplementation(hardhatVerify, implAddress, errorReport) {
try {
console.log(`Verifying implementation: ${implAddress}`);
await hardhatVerify(implAddress);
}
catch (e) {
const message = e instanceof Error ? e.message : String(e);
if (message.toLowerCase().includes('already verified')) {
console.log(`Implementation ${implAddress} already verified.`);
}
else {
recordVerificationError(implAddress, 'implementation', message, errorReport);
}
}
}
async function searchEvent(etherscan, address, possibleContractInfo) {
for (let i = 0; i < possibleContractInfo.length; i++) {
const contractInfo = possibleContractInfo[i];
const txHash = await getContractCreationTxHash(address, contractInfo.event, etherscan);
if (txHash !== undefined) {
return { contractInfo, txHash };
}
}
const events = possibleContractInfo.map(ci => ci.event);
throw new EventsNotFound(address, events);
}
async function attemptVerifyOrFallback(attemptVerify, hardhatVerify, address, errorReport, convertErrorsToWarningsOnFallbackSuccess) {
try {
await attemptVerify();
return;
}
catch (origError) {
if (origError instanceof BytecodeNotMatchArtifact || origError instanceof EventOrFunctionNotFound) {
try {
await hardhatVerify(address);
}
catch (fallbackError) {
const fallbackMessage = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
if (fallbackMessage.toLowerCase().includes('already verified')) {
console.log(`Contract at ${address} already verified.`);
}
else {
if (origError instanceof BytecodeNotMatchArtifact) {
recordVerificationError(address, origError.contractName, origError.message, errorReport);
}
else {
recordError(origError instanceof Error ? origError.message : String(origError), errorReport);
}
recordError(`Failed to verify directly using hardhat verify: ${fallbackMessage}`, errorReport);
return;
}
}
if (convertErrorsToWarningsOnFallbackSuccess) {
errorReport.severity = 'warn';
}
}
else {
throw origError;
}
}
}
async function verifyWithArtifactOrFallback(hre, hardhatVerify, etherscan, address, possibleContractInfo, errorReport, convertErrorsToWarningsOnFallbackSuccess) {
const attemptVerify = () => attemptVerifyWithCreationEvent(hre, etherscan, address, possibleContractInfo, errorReport);
await attemptVerifyOrFallback(attemptVerify, hardhatVerify, address, errorReport, convertErrorsToWarningsOnFallbackSuccess);
}
async function attemptVerifyWithCreationEvent(hre, etherscan, address, possibleContractInfo, errorReport) {
const { contractInfo, txHash } = await searchEvent(etherscan, address, possibleContractInfo);
debug(`verifying contract ${contractInfo.artifact.contractName} at ${address}`);
const connection = await hre.network.getOrCreate();
const tx = await getTransactionByHash(connection.provider, txHash);
if (tx === null) {
throw new UpgradesError(`The transaction hash ${txHash} from the contract's logs was not found on the network`);
}
const constructorArguments = inferConstructorArgs(tx.input, contractInfo.artifact.bytecode);
if (constructorArguments === undefined) {
throw new BytecodeNotMatchArtifact(`Bytecode does not match with the current version of ${contractInfo.artifact.contractName} in the Hardhat Upgrades plugin.`, contractInfo.artifact.contractName);
}
await verifyContractWithConstructorArgs(etherscan, address, contractInfo.artifact, constructorArguments, errorReport);
}
async function verifyContractWithConstructorArgs(etherscan, address, artifact, constructorArguments, errorReport) {
debug(`verifying contract ${address} with constructor args ${constructorArguments}`);
const buildInfo = artifactsBuildInfo;
const params = {
contractAddress: address,
compilerInput: buildInfo.input,
contractName: `${artifact.sourceName}:${artifact.contractName}`,
compilerVersion: `v${buildInfo.solcLongVersion}`,
constructorArguments,
};
try {
const status = await verifyAndGetStatus(params, etherscan);
if (status.success) {
console.log(`Successfully verified contract ${artifact.contractName} at ${address}.`);
}
else {
recordVerificationError(address, artifact.contractName, status.message, errorReport);
}
}
catch (e) {
const message = e instanceof Error ? e.message : String(e);
if (message.toLowerCase().includes('already verified')) {
console.log(`Contract at ${address} already verified.`);
}
else {
recordVerificationError(address, artifact.contractName, message, errorReport);
}
}
}
async function getEventResponse(address, topic, etherscan) {
const params = {
module: 'logs',
action: 'getLogs',
fromBlock: '0',
toBlock: 'latest',
address,
topic0: '0x' + keccak256(Buffer.from(topic)).toString('hex'),
};
const responseBody = await callEtherscanApi(etherscan, params);
if (responseBody.status === RESPONSE_OK) {
const result = responseBody.result;
return result[0];
}
else if (responseBody.message === 'No records found' || responseBody.message === 'No logs found') {
debug(`no result found for event topic ${topic} at address ${address}`);
return undefined;
}
else {
throw new UpgradesError(`Failed to get logs for contract at address ${address}.`, () => `Etherscan returned with message: ${responseBody.message}, reason: ${responseBody.result}`);
}
}
async function getContractCreationTxHash(address, topic, etherscan) {
const eventResponse = await getEventResponse(address, topic, etherscan);
return eventResponse?.transactionHash;
}
async function linkProxyWithImplementationAbi(etherscan, proxyAddress, implAddress, errorReport) {
console.log(`Linking proxy ${proxyAddress} with implementation`);
const params = {
module: 'contract',
action: 'verifyproxycontract',
address: proxyAddress,
expectedimplementation: implAddress,
};
let responseBody = await callEtherscanApi(etherscan, params, { method: 'POST', body: params });
if (responseBody.status === RESPONSE_OK) {
const guid = responseBody.result;
responseBody = await checkProxyVerificationStatus(etherscan, guid);
while (responseBody.result === 'Pending in queue') {
await delay(3000);
responseBody = await checkProxyVerificationStatus(etherscan, guid);
}
}
if (responseBody.status === RESPONSE_OK) {
console.log('Successfully linked proxy to implementation.');
}
else {
recordError(`Failed to link proxy ${proxyAddress} with its implementation. Reason: ${responseBody.result}`, errorReport);
}
}
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function checkProxyVerificationStatus(etherscan, guid) {
const params = {
module: 'contract',
action: 'checkproxyverification',
guid,
};
return (await callEtherscanApi(etherscan, params));
}
function inferConstructorArgs(txInput, creationCode) {
if (txInput.startsWith(creationCode)) {
return txInput.substring(creationCode.length);
}
return undefined;
}
//# sourceMappingURL=verify-proxy.js.map