diff --git a/packages/deploy/hardhat.config.ts b/packages/deploy/hardhat.config.ts index 5d9c342f43..7f2a36af1c 100644 --- a/packages/deploy/hardhat.config.ts +++ b/packages/deploy/hardhat.config.ts @@ -8,6 +8,7 @@ import { addNodeAndMnemonic, skipDeploymentsOnLiveNetworks, } from './utils/hardhatConfig'; +import './tasks/deployHook'; import './tasks/importedPackages'; // Package name : solidity source code path diff --git a/packages/deploy/package.json b/packages/deploy/package.json index 6834a3be71..a3d3fd132b 100644 --- a/packages/deploy/package.json +++ b/packages/deploy/package.json @@ -45,8 +45,11 @@ "@ethersproject/abi": "^5.7.0", "@ethersproject/providers": "^5.7.2", "@nomicfoundation/hardhat-chai-matchers": "1", + "@nomicfoundation/hardhat-ethers": "^3.0.0", "@nomicfoundation/hardhat-network-helpers": "^1.0.8", + "@nomicfoundation/hardhat-verify": "^1.1.0", "@nomiclabs/hardhat-ethers": "^2.2.3", + "@openzeppelin/hardhat-upgrades": "^2.5.0", "@typechain/ethers-v5": "^11.0.0", "@typechain/hardhat": "^8.0.0", "@types/chai": "^4.3.5", diff --git a/packages/deploy/tasks/deployHook.ts b/packages/deploy/tasks/deployHook.ts new file mode 100644 index 0000000000..7241c38675 --- /dev/null +++ b/packages/deploy/tasks/deployHook.ts @@ -0,0 +1,113 @@ +import {extendEnvironment, task} from 'hardhat/config'; +import 'dotenv/config'; +import 'hardhat-deploy'; +import '@openzeppelin/hardhat-upgrades'; +import {HardhatPluginError, lazyFunction} from 'hardhat/plugins'; +import { + DeploymentsExtension, + DeployOptions, + DeployResult, +} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; +import {ContractFactory} from 'ethers'; +import {TASK_DEPLOY} from 'hardhat-deploy'; +import {ValidateUpgradeOptions} from '@openzeppelin/hardhat-upgrades/src/utils/options'; + +declare module 'hardhat/types/runtime' { + interface HardhatRuntimeEnvironment { + _ozVerifyOpts: ValidateUpgradeOptions; + } +} +type ValidationErrorKind = NonNullable< + ValidateUpgradeOptions['unsafeAllow'] +> extends (infer U)[] + ? U + : never; +const unsafeOpts: {[k: string]: ValidationErrorKind} = { + StateVariableAssignment: 'state-variable-assignment', + StateVariableImmutable: 'state-variable-immutable', + ExternalLibraryLinking: 'external-library-linking', + StructDefinition: 'struct-definition', + EnumDefinition: 'enum-definition', + Constructor: 'constructor', + DelegateCall: 'delegatecall', + SelfDestruct: 'selfdestruct', + MissingPublicUpgradeTo: 'missing-public-upgradeto', +}; + +function deployHook( + hre: HardhatRuntimeEnvironment, + orig: DeploymentsExtension +) { + return async ( + name: string, + options: DeployOptions + ): Promise => { + // We assume same name different contract => upgrade. + const deployment = await hre.deployments.getOrNull( + name + '_Implementation' + ); + if (deployment) { + console.log('CHECKING UPGRADE', name, options.contract); + if (!deployment.bytecode) { + throw new HardhatPluginError(`missing bytecode for deployment ${name}`); + } + const oldFactory = new ContractFactory( + deployment.abi, + deployment.bytecode + ); + await hre.upgrades.validateImplementation(oldFactory, hre._ozVerifyOpts); + if (!options.contract || typeof options.contract !== 'string') { + throw new HardhatPluginError(`missing new contract for ${name}`); + } + const newName = options.contract as string; + const artifact = await hre.artifacts.readArtifact(newName); + if (!artifact || !artifact.abi || !artifact.bytecode) { + throw new HardhatPluginError( + `missing bytecode for artifact ${newName}` + ); + } + const newFactory = new ContractFactory(artifact.abi, artifact.bytecode); + await hre.upgrades.validateImplementation(newFactory, hre._ozVerifyOpts); + await hre.upgrades.validateUpgrade( + oldFactory, + newFactory, + hre._ozVerifyOpts + ); + } + return await orig.deploy(name, options); + }; +} + +extendEnvironment((hre) => { + const origClone: DeploymentsExtension = Object.assign({}, hre.deployments); + hre.deployments.deploy = lazyFunction(() => deployHook(hre, origClone)); +}); + +Object.keys(unsafeOpts) + .reduce( + (task, o) => + task.addFlag( + 'unsafeAllow' + o, + 'Selectively disable one or more validation errors' + ), + task(TASK_DEPLOY, 'use defender admin to propose transactions') + ) + .addFlag( + 'unsafeAllowRenames', + 'Configure storage layout check to allow variable renaming' + ) + .addFlag( + 'unsafeSkipStorageCheck', + 'Upgrades the proxy or beacon without first checking for storage layout compatibility errors. This is a dangerous option meant to be used as a last resort' + ) + .setAction(async (args: {[flag: string]: boolean}, hre, runSuper) => { + hre._ozVerifyOpts = {...args}; + hre._ozVerifyOpts.unsafeAllow = []; + for (const o in unsafeOpts) { + if (args['unsafeAllow' + o]) { + hre._ozVerifyOpts.unsafeAllow.push(unsafeOpts[o]); + } + } + return await runSuper(args); + }); diff --git a/packages/deploy/tasks/importedPackages.ts b/packages/deploy/tasks/importedPackages.ts index c2fac0f3ca..2e7a7d865d 100644 --- a/packages/deploy/tasks/importedPackages.ts +++ b/packages/deploy/tasks/importedPackages.ts @@ -21,7 +21,7 @@ declare module 'hardhat/types/runtime' { } declare module 'hardhat/types/config' { interface HardhatUserConfig { - importedPackages: {[name: string]: string}; + importedPackages: {[name: string]: string | string[]}; } } diff --git a/yarn.lock b/yarn.lock index 0cc1fd493a..9e76eb1def 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1306,6 +1306,19 @@ __metadata: languageName: node linkType: hard +"@nomicfoundation/hardhat-ethers@npm:^3.0.0": + version: 3.0.5 + resolution: "@nomicfoundation/hardhat-ethers@npm:3.0.5" + dependencies: + debug: ^4.1.1 + lodash.isequal: ^4.5.0 + peerDependencies: + ethers: ^6.1.0 + hardhat: ^2.0.0 + checksum: 34b092dfec68f8d8673c96af717660327edc814bc5c9cdb5bc1f82d5bde2b18bc9b9d3499a632784a3d4f2505ac174217e55d31b546b7eaa77a5bb30b3c80bb4 + languageName: node + linkType: hard + "@nomicfoundation/hardhat-ethers@npm:^3.0.3, @nomicfoundation/hardhat-ethers@npm:^3.0.4": version: 3.0.4 resolution: "@nomicfoundation/hardhat-ethers@npm:3.0.4" @@ -1382,7 +1395,7 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/hardhat-verify@npm:^1.0.0": +"@nomicfoundation/hardhat-verify@npm:^1.0.0, @nomicfoundation/hardhat-verify@npm:^1.1.0": version: 1.1.1 resolution: "@nomicfoundation/hardhat-verify@npm:1.1.1" dependencies: @@ -1867,7 +1880,7 @@ __metadata: languageName: node linkType: hard -"@openzeppelin/defender-admin-client@npm:^1.48.0": +"@openzeppelin/defender-admin-client@npm:^1.48.0, @openzeppelin/defender-admin-client@npm:^1.52.0": version: 1.52.0 resolution: "@openzeppelin/defender-admin-client@npm:1.52.0" dependencies: @@ -1880,7 +1893,7 @@ __metadata: languageName: node linkType: hard -"@openzeppelin/defender-base-client@npm:1.52.0, @openzeppelin/defender-base-client@npm:^1.48.0": +"@openzeppelin/defender-base-client@npm:1.52.0, @openzeppelin/defender-base-client@npm:^1.48.0, @openzeppelin/defender-base-client@npm:^1.52.0": version: 1.52.0 resolution: "@openzeppelin/defender-base-client@npm:1.52.0" dependencies: @@ -1916,6 +1929,16 @@ __metadata: languageName: node linkType: hard +"@openzeppelin/defender-sdk-base-client@npm:^1.6.0": + version: 1.6.0 + resolution: "@openzeppelin/defender-sdk-base-client@npm:1.6.0" + dependencies: + amazon-cognito-identity-js: ^6.3.6 + async-retry: ^1.3.3 + checksum: 4516d16d18d42f6a43a856c9a8b5569ed17482e2714d80aaca9581ba7345deec4e654844933877e2ff0addad2594e127c2ffeac0d092bbbebb0b2cc7e984fce1 + languageName: node + linkType: hard + "@openzeppelin/defender-sdk-deploy-client@npm:^1.2.0": version: 1.5.0 resolution: "@openzeppelin/defender-sdk-deploy-client@npm:1.5.0" @@ -1928,6 +1951,18 @@ __metadata: languageName: node linkType: hard +"@openzeppelin/defender-sdk-deploy-client@npm:^1.5.0": + version: 1.6.0 + resolution: "@openzeppelin/defender-sdk-deploy-client@npm:1.6.0" + dependencies: + "@ethersproject/abi": ^5.7.0 + "@openzeppelin/defender-sdk-base-client": ^1.6.0 + axios: ^1.4.0 + lodash: ^4.17.21 + checksum: 32ed9c36affb976460945b1ecdb6f9c1d7e505e2f46e04943de1fae6aeb4cb0410205cff51c6552ab757d167ffae3a7eab0631580c9dbccb3fa7649220a930ed + languageName: node + linkType: hard + "@openzeppelin/hardhat-upgrades@npm:^1.28.0": version: 1.28.0 resolution: "@openzeppelin/hardhat-upgrades@npm:1.28.0" @@ -1980,6 +2015,34 @@ __metadata: languageName: node linkType: hard +"@openzeppelin/hardhat-upgrades@npm:^2.5.0": + version: 2.5.0 + resolution: "@openzeppelin/hardhat-upgrades@npm:2.5.0" + dependencies: + "@openzeppelin/defender-admin-client": ^1.52.0 + "@openzeppelin/defender-base-client": ^1.52.0 + "@openzeppelin/defender-sdk-base-client": ^1.5.0 + "@openzeppelin/defender-sdk-deploy-client": ^1.5.0 + "@openzeppelin/upgrades-core": ^1.31.2 + chalk: ^4.1.0 + debug: ^4.1.1 + ethereumjs-util: ^7.1.5 + proper-lockfile: ^4.1.1 + undici: ^5.14.0 + peerDependencies: + "@nomicfoundation/hardhat-ethers": ^3.0.0 + "@nomicfoundation/hardhat-verify": ^1.1.0 + ethers: ^6.6.0 + hardhat: ^2.0.2 + peerDependenciesMeta: + "@nomicfoundation/hardhat-verify": + optional: true + bin: + migrate-oz-cli-project: dist/scripts/migrate-oz-cli-project.js + checksum: 2d431ad84635ddbefeb83e1d651e5ec49c1574c7cc70cf67ad98c1c8c75d359dc73d6a1e1b99c2543784a25f57b38b4cf6fae3ca621318f320a64c496df5696f + languageName: node + linkType: hard + "@openzeppelin/platform-deploy-client@npm:^0.8.0": version: 0.8.0 resolution: "@openzeppelin/platform-deploy-client@npm:0.8.0" @@ -2029,6 +2092,24 @@ __metadata: languageName: node linkType: hard +"@openzeppelin/upgrades-core@npm:^1.31.2": + version: 1.31.3 + resolution: "@openzeppelin/upgrades-core@npm:1.31.3" + dependencies: + cbor: ^9.0.0 + chalk: ^4.1.0 + compare-versions: ^6.0.0 + debug: ^4.1.1 + ethereumjs-util: ^7.0.3 + minimist: ^1.2.7 + proper-lockfile: ^4.1.1 + solidity-ast: ^0.4.51 + bin: + openzeppelin-upgrades-core: dist/cli/cli.js + checksum: e7ab98bb822b7b69b0a5f333fd9113e13d05b995ccf880ea8e15406c2a5a0a8c0c61205e8b548859fac779c646d621c9315112c4bc16d7f5001b7fbf29972644 + languageName: node + linkType: hard + "@parcel/watcher@npm:2.0.4": version: 2.0.4 resolution: "@parcel/watcher@npm:2.0.4" @@ -2337,8 +2418,11 @@ __metadata: "@ethersproject/abi": ^5.7.0 "@ethersproject/providers": ^5.7.2 "@nomicfoundation/hardhat-chai-matchers": 1 + "@nomicfoundation/hardhat-ethers": ^3.0.0 "@nomicfoundation/hardhat-network-helpers": ^1.0.8 + "@nomicfoundation/hardhat-verify": ^1.1.0 "@nomiclabs/hardhat-ethers": ^2.2.3 + "@openzeppelin/hardhat-upgrades": ^2.5.0 "@sandbox-smart-contracts/asset": "*" "@sandbox-smart-contracts/core": "*" "@sandbox-smart-contracts/dependency-operator-filter": "*"