diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index e425bc0d2..db0a7fb25 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -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)) diff --git a/packages/core/hardhat/separate-test-contracts.js b/packages/core/hardhat/separate-test-contracts.js index e397c2f53..f8d5a1ba1 100644 --- a/packages/core/hardhat/separate-test-contracts.js +++ b/packages/core/hardhat/separate-test-contracts.js @@ -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; + } +} diff --git a/packages/core/package.json b/packages/core/package.json index 15a3b8f36..b6049d77a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -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", diff --git a/packages/core/scripts/copy-build-info.js b/packages/core/scripts/copy-build-info.js index 75a899807..84d003477 100644 --- a/packages/core/scripts/copy-build-info.js +++ b/packages/core/scripts/copy-build-info.js @@ -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')); @@ -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'); diff --git a/packages/plugin-hardhat/CHANGELOG.md b/packages/plugin-hardhat/CHANGELOG.md index 5832f27c1..af1c9cb0d 100644 --- a/packages/plugin-hardhat/CHANGELOG.md +++ b/packages/plugin-hardhat/CHANGELOG.md @@ -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)) diff --git a/packages/plugin-hardhat/src/verify-proxy.ts b/packages/plugin-hardhat/src/verify-proxy.ts index ffb838ba9..50ae735d7 100644 --- a/packages/plugin-hardhat/src/verify-proxy.ts +++ b/packages/plugin-hardhat/src/verify-proxy.ts @@ -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'; @@ -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 }, }; /** @@ -383,7 +408,7 @@ async function verifyContractWithCreationEvent( errors, ); } else { - await verifyContractWithConstructorArgs(etherscanApi, address, contractInfo.artifact, constructorArguments, errors); + await verifyContractWithConstructorArgs(etherscanApi, address, contractInfo, constructorArguments, errors); } } @@ -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, };