Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use separate Solc inputs to verify different proxy contracts #755

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## Unreleased

- Use separate Solc inputs to verify different proxy contracts. ([#755](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/755))

## 1.24.1 (2023-03-02)

- Remove test contracts from source code verification. ([#751](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/751))
Expand Down
41 changes: 24 additions & 17 deletions packages/core/hardhat/separate-test-contracts.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
// We have some test contracts that contain unsafe code and should not be included for source code verification.
// Thus, we force Hardhat to compile them in a separate compilation job so that they would appear in a separate
// compilation artifact file.
// Force Hardhat to compile each proxy-related contract in a separate compilation job so that they would each
// appear in a separate compilation artifact file.

const { task } = require('hardhat/config');
const { TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOB_FOR_FILE } = require('hardhat/builtin-tasks/task-names');

const marker = Symbol('test');
const markedCache = new WeakMap();

task(TASK_COMPILE_SOLIDITY_GET_COMPILATION_JOB_FOR_FILE, async (params, _, runSuper) => {
const job = await runSuper(params);
// If the file is not a proxy contract, we make a copy of the config and mark it, which will cause it to get
// compiled separately (along with the other marked files).
// Dependencies of proxy contracts would be automatically included in the proxy contracts compilation.
if (!params.file.sourceName.startsWith('@openzeppelin/contracts/proxy/')) {
const originalConfig = job.solidityConfig;
let markedConfig = markedCache.get(originalConfig);
if (markedConfig === undefined) {
markedConfig = { ...originalConfig, [marker]: true };
markedCache.set(originalConfig, markedConfig);
}
job.solidityConfig = markedConfig;
// If the file is a proxy-related contract, we make a copy of the config and mark it,
// which will cause it to get compiled separately.
// Dependencies of each contract would be automatically included in the same compilation.
const marker = getProxyContractMarker(params.file.sourceName);
if (marker !== undefined) {
job.solidityConfig = { ...job.solidityConfig, [marker]: true };
}
return job;
});

function getProxyContractMarker(sourceName) {
if (sourceName === '@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol') {
return Symbol('BeaconProxy');
} else if (sourceName === '@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol') {
return Symbol('UpgradeableBeacon');
} else if (sourceName === '@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol') {
return Symbol('ERC1967Proxy');
} else if (sourceName === '@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol') {
return Symbol('ProxyAdmin');
} else if (sourceName === '@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol') {
return Symbol('TransparentUpgradeableProxy');
} else {
return undefined;
}
}
6 changes: 5 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@
"/artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol",
"/artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json",
"/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json",
"/artifacts/build-info.json"
"/artifacts/proxy-build-info/BeaconProxy.json",
"/artifacts/proxy-build-info/UpgradeableBeacon.json",
"/artifacts/proxy-build-info/ERC1967Proxy.json",
"/artifacts/proxy-build-info/ProxyAdmin.json",
"/artifacts/proxy-build-info/TransparentUpgradeableProxy.json"
],
"scripts": {
"clean": "hardhat clean && rimraf dist *.tsbuildinfo",
Expand Down
86 changes: 46 additions & 40 deletions packages/core/scripts/copy-build-info.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const fs = require('fs');
const assert = require('assert');
const path = require('path');

function readJSON(path) {
return JSON.parse(fs.readFileSync(path, 'utf8'));
Expand All @@ -11,55 +12,60 @@ function writeJSON(path, data) {
fs.writeFileSync(path, JSON.stringify(data, null, 2));
}

function hasProperty(obj, prop) {
return prop in obj;
}

function hasPropertyStartsWith(obj, prefix) {
return Object.keys(obj).some(item => {
return typeof item === 'string' && item.startsWith(prefix);
});
}

const buildInfoField = readJSON(
'artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.dbg.json',
).buildInfo;
const jsonRelativePath = `artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol/${buildInfoField}`;
function getBuildInfoFile(contractArtifactSol, contractName) {
const buildInfoRelativePath = readJSON(`${contractArtifactSol}/${contractName}.dbg.json`).buildInfo;
return `${contractArtifactSol}/${buildInfoRelativePath}`;
}

// Assert that all deployable proxy artifacts use the same build-info file
assert(
buildInfoField ===
readJSON('artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol/BeaconProxy.dbg.json').buildInfo,
);
assert(
buildInfoField ===
readJSON('artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.dbg.json')
.buildInfo,
);
assert(
buildInfoField ===
readJSON(
'artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.dbg.json',
).buildInfo,
);
assert(
buildInfoField ===
readJSON('artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.dbg.json').buildInfo,
);
function copyMinimalBuildInfo(fromFile, toFile) {
const buildInfo = readJSON(fromFile);

const reducedInfo = { solcLongVersion: buildInfo.solcLongVersion, input: buildInfo.input };
const sources = reducedInfo.input.sources;

const buildInfo = readJSON(jsonRelativePath);
const reducedInfo = { solcLongVersion: buildInfo.solcLongVersion, input: buildInfo.input };
// Assert that the build-info file does NOT contain test contracts
assert(!hasPropertyStartsWith(sources, 'contracts/test'));

const sources = reducedInfo.input.sources;
writeJSON(toFile, reducedInfo);
}

// Assert that all deployable proxy artifacts exist in ERC1967's build-info file
assert(hasProperty(sources, '@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol'));
assert(hasProperty(sources, '@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol'));
assert(hasProperty(sources, '@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol'));
assert(hasProperty(sources, '@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol'));
assert(hasProperty(sources, '@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol'));
const BeaconProxy = getBuildInfoFile('artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol', 'BeaconProxy');
const UpgradeableBeacon = getBuildInfoFile(
'artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol',
'UpgradeableBeacon',
);
const ERC1967Proxy = getBuildInfoFile(
'artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol',
'ERC1967Proxy',
);
const ProxyAdmin = getBuildInfoFile('artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol', 'ProxyAdmin');
const TransparentUpgradeableProxy = getBuildInfoFile(
'artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol',
'TransparentUpgradeableProxy',
);

// Assert that the build-info file does NOT contain test contracts
assert(!hasPropertyStartsWith(sources, 'contracts/test'));
// Assert that each proxy artifact's build-info file is different
const set = new Set();
set.add(path.parse(BeaconProxy).base);
set.add(path.parse(UpgradeableBeacon).base);
set.add(path.parse(ERC1967Proxy).base);
set.add(path.parse(ProxyAdmin).base);
set.add(path.parse(TransparentUpgradeableProxy).base);
assert(set.size === 5);

writeJSON('artifacts/build-info.json', reducedInfo);
fs.mkdir('artifacts/proxy-build-info', { recursive: true }, err => {
if (err) {
throw err;
}
});
copyMinimalBuildInfo(BeaconProxy, 'artifacts/proxy-build-info/BeaconProxy.json');
copyMinimalBuildInfo(UpgradeableBeacon, 'artifacts/proxy-build-info/UpgradeableBeacon.json');
copyMinimalBuildInfo(ERC1967Proxy, 'artifacts/proxy-build-info/ERC1967Proxy.json');
copyMinimalBuildInfo(ProxyAdmin, 'artifacts/proxy-build-info/ProxyAdmin.json');
copyMinimalBuildInfo(TransparentUpgradeableProxy, 'artifacts/proxy-build-info/TransparentUpgradeableProxy.json');
4 changes: 4 additions & 0 deletions packages/plugin-hardhat/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## Unreleased

- Use separate Solc inputs to verify different proxy contracts. ([#755](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/755))

## 1.22.1 (2023-01-18)

- Handle getLogs error for Blockscout explorer. ([#706](https://github.com/OpenZeppelin/openzeppelin-upgrades/pull/706))
Expand Down
50 changes: 39 additions & 11 deletions packages/plugin-hardhat/src/verify-proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,23 @@ import {
isBeaconProxy,
isEmptySlot,
} from '@openzeppelin/upgrades-core';
import artifactsBuildInfo from '@openzeppelin/upgrades-core/artifacts/build-info.json';

import { HardhatRuntimeEnvironment, RunSuperFunction } from 'hardhat/types';

import ERC1967Proxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json';
import ERC1967ProxyBuildInfo from '@openzeppelin/upgrades-core/artifacts/proxy-build-info/ERC1967Proxy.json';

import BeaconProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol/BeaconProxy.json';
import BeaconProxyBuildInfo from '@openzeppelin/upgrades-core/artifacts/proxy-build-info/BeaconProxy.json';

import UpgradeableBeacon from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json';
import UpgradeableBeaconBuildInfo from '@openzeppelin/upgrades-core/artifacts/proxy-build-info/UpgradeableBeacon.json';

import TransparentUpgradeableProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json';
import TransparentUpgradeableProxyBuildInfo from '@openzeppelin/upgrades-core/artifacts/proxy-build-info/TransparentUpgradeableProxy.json';

import ProxyAdmin from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json';
import ProxyAdminBuildInfo from '@openzeppelin/upgrades-core/artifacts/proxy-build-info/ProxyAdmin.json';

import { keccak256 } from 'ethereumjs-util';

Expand All @@ -50,17 +58,34 @@ interface ContractArtifact {
interface VerifiableContractInfo {
artifact: ContractArtifact;
event: string;
buildInfo: BuildInfo;
}

/**
* Hardhat build info file content
*/
interface BuildInfo {
solcLongVersion: string;
input: any;
}

/**
* The proxy-related contracts and their corresponding events that may have been deployed the current version of this plugin.
*/
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)' },
erc1967proxy: { artifact: ERC1967Proxy, event: 'Upgraded(address)', buildInfo: ERC1967ProxyBuildInfo },
beaconProxy: { artifact: BeaconProxy, event: 'BeaconUpgraded(address)', buildInfo: BeaconProxyBuildInfo },
upgradeableBeacon: {
artifact: UpgradeableBeacon,
event: 'OwnershipTransferred(address,address)',
buildInfo: UpgradeableBeaconBuildInfo,
},
transparentUpgradeableProxy: {
artifact: TransparentUpgradeableProxy,
event: 'AdminChanged(address,address)',
buildInfo: TransparentUpgradeableProxyBuildInfo,
},
proxyAdmin: { artifact: ProxyAdmin, event: 'OwnershipTransferred(address,address)', buildInfo: ProxyAdminBuildInfo },
};

/**
Expand Down Expand Up @@ -383,7 +408,7 @@ async function verifyContractWithCreationEvent(
errors,
);
} else {
await verifyContractWithConstructorArgs(etherscanApi, address, contractInfo.artifact, constructorArguments, errors);
await verifyContractWithConstructorArgs(etherscanApi, address, contractInfo, constructorArguments, errors);
}
}

Expand All @@ -392,25 +417,28 @@ async function verifyContractWithCreationEvent(
*
* @param etherscanApi The Etherscan API config
* @param address The address of the contract to verify
* @param artifact The contract artifact to use for verification.
* @param contractInfo The contract info to use for verification.
* @param constructorArguments The constructor arguments to use for verification.
*/
async function verifyContractWithConstructorArgs(
etherscanApi: EtherscanAPIConfig,
address: any,
artifact: ContractArtifact,
contractInfo: VerifiableContractInfo,
constructorArguments: string,
errors: string[],
) {
debug(`verifying contract ${address} with constructor args ${constructorArguments}`);

const buildInfo = contractInfo.buildInfo;
const artifact = contractInfo.artifact;

const params = {
apiKey: etherscanApi.key,
contractAddress: address,
sourceCode: JSON.stringify(artifactsBuildInfo.input),
sourceCode: JSON.stringify(buildInfo.input),
sourceName: artifact.sourceName,
contractName: artifact.contractName,
compilerVersion: `v${artifactsBuildInfo.solcLongVersion}`,
compilerVersion: `v${buildInfo.solcLongVersion}`,
constructorArguments: constructorArguments,
};

Expand Down