@openzeppelin/hardhat-upgrades
Advanced tools
Comparing version 1.28.0 to 3.0.2
import type { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
import { Contract, Signer } from 'ethers'; | ||
export type ChangeAdminFunction = (proxyAddress: string, newAdmin: string, signer?: Signer) => Promise<void>; | ||
export type TransferProxyAdminOwnershipFunction = (newOwner: string, signer?: Signer) => Promise<void>; | ||
import { EthersDeployOptions } from './utils'; | ||
export type ChangeAdminFunction = (proxyAddress: string, newAdmin: string, signer?: Signer, opts?: EthersDeployOptions) => Promise<void>; | ||
export type TransferProxyAdminOwnershipFunction = (proxyAddress: string, newOwner: string, signer?: Signer, opts?: EthersDeployOptions) => Promise<void>; | ||
export type GetInstanceFunction = (signer?: Signer) => Promise<Contract>; | ||
export declare function makeChangeProxyAdmin(hre: HardhatRuntimeEnvironment, platformModule: boolean): ChangeAdminFunction; | ||
export declare function makeTransferProxyAdminOwnership(hre: HardhatRuntimeEnvironment, platformModule: boolean): TransferProxyAdminOwnershipFunction; | ||
export declare function makeGetInstanceFunction(hre: HardhatRuntimeEnvironment): GetInstanceFunction; | ||
export declare function getManifestAdmin(hre: HardhatRuntimeEnvironment, signer?: Signer): Promise<Contract>; | ||
export declare function makeChangeProxyAdmin(hre: HardhatRuntimeEnvironment, defenderModule: boolean): ChangeAdminFunction; | ||
export declare function makeTransferProxyAdminOwnership(hre: HardhatRuntimeEnvironment, defenderModule: boolean): TransferProxyAdminOwnershipFunction; | ||
//# sourceMappingURL=admin.d.ts.map |
@@ -6,28 +6,28 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getManifestAdmin = exports.makeGetInstanceFunction = exports.makeTransferProxyAdminOwnership = exports.makeChangeProxyAdmin = void 0; | ||
exports.makeTransferProxyAdminOwnership = exports.makeChangeProxyAdmin = void 0; | ||
const chalk_1 = __importDefault(require("chalk")); | ||
const upgrades_core_1 = require("@openzeppelin/upgrades-core"); | ||
const utils_1 = require("./utils"); | ||
const utils_2 = require("./platform/utils"); | ||
const utils_2 = require("./defender/utils"); | ||
const SUCCESS_CHECK = chalk_1.default.green('✔') + ' '; | ||
const FAILURE_CROSS = chalk_1.default.red('✘') + ' '; | ||
function makeChangeProxyAdmin(hre, platformModule) { | ||
return async function changeProxyAdmin(proxyAddress, newAdmin, signer) { | ||
(0, utils_2.disablePlatform)(hre, platformModule, {}, changeProxyAdmin.name); | ||
const admin = await getManifestAdmin(hre, signer); | ||
function makeChangeProxyAdmin(hre, defenderModule) { | ||
return async function changeProxyAdmin(proxyAddress, newAdmin, signer, opts = {}) { | ||
(0, utils_2.disableDefender)(hre, defenderModule, {}, changeProxyAdmin.name); | ||
const proxyAdminAddress = await (0, upgrades_core_1.getAdminAddress)(hre.network.provider, proxyAddress); | ||
if (admin.address !== proxyAdminAddress) { | ||
throw new Error('Proxy admin is not the one registered in the network manifest'); | ||
} | ||
else if (admin.address !== newAdmin) { | ||
await admin.changeProxyAdmin(proxyAddress, newAdmin); | ||
} | ||
// Only compatible with v4 admins | ||
const admin = await (0, utils_1.attachProxyAdminV4)(hre, proxyAdminAddress, signer); | ||
const overrides = opts.txOverrides ? [opts.txOverrides] : []; | ||
await admin.changeProxyAdmin(proxyAddress, newAdmin, ...overrides); | ||
}; | ||
} | ||
exports.makeChangeProxyAdmin = makeChangeProxyAdmin; | ||
function makeTransferProxyAdminOwnership(hre, platformModule) { | ||
return async function transferProxyAdminOwnership(newOwner, signer) { | ||
(0, utils_2.disablePlatform)(hre, platformModule, {}, transferProxyAdminOwnership.name); | ||
const admin = await getManifestAdmin(hre, signer); | ||
await admin.transferOwnership(newOwner); | ||
function makeTransferProxyAdminOwnership(hre, defenderModule) { | ||
return async function transferProxyAdminOwnership(proxyAddress, newOwner, signer, opts = {}) { | ||
(0, utils_2.disableDefender)(hre, defenderModule, {}, transferProxyAdminOwnership.name); | ||
const proxyAdminAddress = await (0, upgrades_core_1.getAdminAddress)(hre.network.provider, proxyAddress); | ||
// Compatible with both v4 and v5 admins since they both have transferOwnership | ||
const admin = await (0, utils_1.attachProxyAdminV4)(hre, proxyAdminAddress, signer); | ||
const overrides = opts.txOverrides ? [opts.txOverrides] : []; | ||
await admin.transferOwnership(newOwner, ...overrides); | ||
const { provider } = hre.network; | ||
@@ -37,7 +37,7 @@ const manifest = await upgrades_core_1.Manifest.forNetwork(provider); | ||
for (const { address, kind } of proxies) { | ||
if (admin.address == (await (0, upgrades_core_1.getAdminAddress)(provider, address))) { | ||
console.log(SUCCESS_CHECK + `${address} (${kind}) proxy ownership transfered through admin proxy`); | ||
if ((await admin.getAddress()) == (await (0, upgrades_core_1.getAdminAddress)(provider, address))) { | ||
console.log(SUCCESS_CHECK + `${address} (${kind}) proxy ownership transferred through proxy admin`); | ||
} | ||
else { | ||
console.log(FAILURE_CROSS + `${address} (${kind}) proxy ownership not affected by admin proxy`); | ||
console.log(FAILURE_CROSS + `${address} (${kind}) proxy ownership not affected by proxy admin`); | ||
} | ||
@@ -48,19 +48,2 @@ } | ||
exports.makeTransferProxyAdminOwnership = makeTransferProxyAdminOwnership; | ||
function makeGetInstanceFunction(hre) { | ||
return async function getInstance(signer) { | ||
return await getManifestAdmin(hre, signer); | ||
}; | ||
} | ||
exports.makeGetInstanceFunction = makeGetInstanceFunction; | ||
async function getManifestAdmin(hre, signer) { | ||
const manifest = await upgrades_core_1.Manifest.forNetwork(hre.network.provider); | ||
const manifestAdmin = await manifest.getAdmin(); | ||
const proxyAdminAddress = manifestAdmin?.address; | ||
if (proxyAdminAddress === undefined) { | ||
throw new Error('No ProxyAdmin was found in the network manifest'); | ||
} | ||
const AdminFactory = await (0, utils_1.getProxyAdminFactory)(hre, signer); | ||
return AdminFactory.attach(proxyAdminAddress); | ||
} | ||
exports.getManifestAdmin = getManifestAdmin; | ||
//# sourceMappingURL=admin.js.map |
@@ -8,3 +8,3 @@ import type { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
} | ||
export declare function makeDeployBeaconProxy(hre: HardhatRuntimeEnvironment, platformModule: boolean): DeployBeaconProxyFunction; | ||
export declare function makeDeployBeaconProxy(hre: HardhatRuntimeEnvironment, defenderModule: boolean): DeployBeaconProxyFunction; | ||
//# sourceMappingURL=deploy-beacon-proxy.d.ts.map |
@@ -7,5 +7,5 @@ "use strict"; | ||
const utils_1 = require("./utils"); | ||
const utils_2 = require("./platform/utils"); | ||
const utils_2 = require("./defender/utils"); | ||
const contract_instance_1 = require("./utils/contract-instance"); | ||
function makeDeployBeaconProxy(hre, platformModule) { | ||
function makeDeployBeaconProxy(hre, defenderModule) { | ||
return async function deployBeaconProxy(beacon, attachTo, args = [], opts = {}) { | ||
@@ -19,3 +19,3 @@ if (!(attachTo instanceof ethers_1.ContractFactory)) { | ||
} | ||
opts = (0, utils_2.enablePlatform)(hre, platformModule, opts); | ||
opts = (0, utils_2.enableDefender)(hre, defenderModule, opts); | ||
const { provider } = hre.network; | ||
@@ -27,3 +27,3 @@ const manifest = await upgrades_core_1.Manifest.forNetwork(provider); | ||
opts.kind = 'beacon'; | ||
const beaconAddress = (0, utils_1.getContractAddress)(beacon); | ||
const beaconAddress = await (0, utils_1.getContractAddress)(beacon); | ||
if (!(await (0, upgrades_core_1.isBeacon)(provider, beaconAddress))) { | ||
@@ -39,3 +39,3 @@ throw new upgrades_core_1.DeployBeaconProxyUnsupportedError(beaconAddress); | ||
} | ||
const BeaconProxyFactory = await (0, utils_1.getBeaconProxyFactory)(hre, attachTo.signer); | ||
const BeaconProxyFactory = await (0, utils_1.getBeaconProxyFactory)(hre, (0, utils_1.getSigner)(attachTo.runner)); | ||
const proxyDeployment = Object.assign({ kind: opts.kind }, await (0, utils_1.deploy)(hre, opts, BeaconProxyFactory, beaconAddress, data)); | ||
@@ -42,0 +42,0 @@ await manifest.addProxy(proxyDeployment); |
@@ -7,3 +7,3 @@ import type { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
} | ||
export declare function makeDeployBeacon(hre: HardhatRuntimeEnvironment, platformModule: boolean): DeployBeaconFunction; | ||
export declare function makeDeployBeacon(hre: HardhatRuntimeEnvironment, defenderModule: boolean): DeployBeaconFunction; | ||
//# sourceMappingURL=deploy-beacon.d.ts.map |
@@ -5,10 +5,14 @@ "use strict"; | ||
const utils_1 = require("./utils"); | ||
const utils_2 = require("./platform/utils"); | ||
function makeDeployBeacon(hre, platformModule) { | ||
const utils_2 = require("./defender/utils"); | ||
const ethers_1 = require("./utils/ethers"); | ||
const initial_owner_1 = require("./utils/initial-owner"); | ||
function makeDeployBeacon(hre, defenderModule) { | ||
return async function deployBeacon(ImplFactory, opts = {}) { | ||
(0, utils_2.disablePlatform)(hre, platformModule, opts, deployBeacon.name); | ||
(0, utils_2.disableDefender)(hre, defenderModule, opts, deployBeacon.name); | ||
const { impl } = await (0, utils_1.deployBeaconImpl)(hre, ImplFactory, opts); | ||
const UpgradeableBeaconFactory = await (0, utils_1.getUpgradeableBeaconFactory)(hre, ImplFactory.signer); | ||
const beaconDeployment = await (0, utils_1.deploy)(hre, opts, UpgradeableBeaconFactory, impl); | ||
const beaconContract = UpgradeableBeaconFactory.attach(beaconDeployment.address); | ||
const signer = (0, ethers_1.getSigner)(ImplFactory.runner); | ||
const UpgradeableBeaconFactory = await (0, utils_1.getUpgradeableBeaconFactory)(hre, signer); | ||
const initialOwner = await (0, initial_owner_1.getInitialOwner)(opts, signer); | ||
const beaconDeployment = await (0, utils_1.deploy)(hre, opts, UpgradeableBeaconFactory, impl, initialOwner); | ||
const beaconContract = (0, ethers_1.attach)(UpgradeableBeaconFactory, beaconDeployment.address); | ||
// @ts-ignore Won't be readonly because beaconContract was created through attach. | ||
@@ -15,0 +19,0 @@ beaconContract.deployTransaction = beaconDeployment.deployTransaction; |
@@ -8,3 +8,3 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
} | ||
export declare function makeDeployContract(hre: HardhatRuntimeEnvironment, platformModule: boolean): DeployContractFunction; | ||
export declare function makeDeployContract(hre: HardhatRuntimeEnvironment, defenderModule: boolean): DeployContractFunction; | ||
//# sourceMappingURL=deploy-contract.d.ts.map |
@@ -6,3 +6,3 @@ "use strict"; | ||
const deploy_impl_1 = require("./utils/deploy-impl"); | ||
const utils_2 = require("./platform/utils"); | ||
const utils_2 = require("./defender/utils"); | ||
const upgrades_core_1 = require("@openzeppelin/upgrades-core"); | ||
@@ -26,3 +26,3 @@ const contract_instance_1 = require("./utils/contract-instance"); | ||
} | ||
function makeDeployContract(hre, platformModule) { | ||
function makeDeployContract(hre, defenderModule) { | ||
return async function deployContract(Contract, args = [], opts = {}) { | ||
@@ -33,5 +33,5 @@ if (!Array.isArray(args)) { | ||
} | ||
opts = (0, utils_2.enablePlatform)(hre, platformModule, opts); | ||
if (!opts.usePlatformDeploy) { | ||
throw new Error(`The ${deployContract.name} function cannot have the \`usePlatformDeploy\` option disabled.`); | ||
opts = (0, utils_2.enableDefender)(hre, defenderModule, opts); | ||
if (!opts.useDefenderDeploy) { | ||
throw new Error(`The ${deployContract.name} function cannot have the \`useDefenderDeploy\` option disabled.`); | ||
} | ||
@@ -38,0 +38,0 @@ if (opts.constructorArgs !== undefined) { |
@@ -5,4 +5,4 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
export type DeployImplementationFunction = (ImplFactory: ContractFactory, opts?: DeployImplementationOptions) => Promise<DeployImplementationResponse>; | ||
export type DeployImplementationResponse = string | ethers.providers.TransactionResponse; | ||
export declare function makeDeployImplementation(hre: HardhatRuntimeEnvironment, platformModule: boolean): DeployImplementationFunction; | ||
export type DeployImplementationResponse = string | ethers.TransactionResponse; | ||
export declare function makeDeployImplementation(hre: HardhatRuntimeEnvironment, defenderModule: boolean): DeployImplementationFunction; | ||
//# sourceMappingURL=deploy-implementation.d.ts.map |
@@ -5,8 +5,8 @@ "use strict"; | ||
const deploy_impl_1 = require("./utils/deploy-impl"); | ||
const utils_1 = require("./platform/utils"); | ||
function makeDeployImplementation(hre, platformModule) { | ||
const utils_1 = require("./defender/utils"); | ||
function makeDeployImplementation(hre, defenderModule) { | ||
return async function deployImplementation(ImplFactory, opts = {}) { | ||
opts = (0, utils_1.enablePlatform)(hre, platformModule, opts); | ||
opts = (0, utils_1.enableDefender)(hre, defenderModule, opts); | ||
const deployedImpl = await (0, deploy_impl_1.deployUpgradeableImpl)(hre, ImplFactory, opts); | ||
if (opts.getTxResponse && deployedImpl.txResponse !== undefined) { | ||
if (opts.getTxResponse && deployedImpl.txResponse) { | ||
return deployedImpl.txResponse; | ||
@@ -13,0 +13,0 @@ } |
@@ -8,3 +8,3 @@ import type { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
} | ||
export declare function makeDeployProxy(hre: HardhatRuntimeEnvironment, platformModule: boolean): DeployFunction; | ||
export declare function makeDeployProxy(hre: HardhatRuntimeEnvironment, defenderModule: boolean): DeployFunction; | ||
//# sourceMappingURL=deploy-proxy.d.ts.map |
@@ -6,5 +6,6 @@ "use strict"; | ||
const utils_1 = require("./utils"); | ||
const utils_2 = require("./platform/utils"); | ||
const utils_2 = require("./defender/utils"); | ||
const contract_instance_1 = require("./utils/contract-instance"); | ||
function makeDeployProxy(hre, platformModule) { | ||
const initial_owner_1 = require("./utils/initial-owner"); | ||
function makeDeployProxy(hre, defenderModule) { | ||
return async function deployProxy(ImplFactory, args = [], opts = {}) { | ||
@@ -15,3 +16,3 @@ if (!Array.isArray(args)) { | ||
} | ||
opts = (0, utils_2.enablePlatform)(hre, platformModule, opts); | ||
opts = (0, utils_2.enableDefender)(hre, defenderModule, opts); | ||
const { provider } = hre.network; | ||
@@ -22,4 +23,4 @@ const manifest = await upgrades_core_1.Manifest.forNetwork(provider); | ||
const data = (0, utils_1.getInitializerData)(contractInterface, args, opts.initializer); | ||
if (kind === 'uups') { | ||
if (await manifest.getAdmin()) { | ||
if (await manifest.getAdmin()) { | ||
if (kind === 'uups') { | ||
(0, upgrades_core_1.logWarning)(`A proxy admin was previously deployed on this network`, [ | ||
@@ -30,3 +31,10 @@ `This is not natively used with the current kind of proxy ('uups').`, | ||
} | ||
else if (kind === 'transparent') { | ||
(0, upgrades_core_1.logWarning)(`A proxy admin was previously deployed on this network`, [ | ||
`This is not used with new transparent proxy deployments, since new transparent proxies deploy their own admins.`, | ||
`Changes to the previous admin will have no effect on this new proxy.`, | ||
]); | ||
} | ||
} | ||
const signer = (0, utils_1.getSigner)(ImplFactory.runner); | ||
let proxyDeployment; | ||
@@ -38,3 +46,6 @@ switch (kind) { | ||
case 'uups': { | ||
const ProxyFactory = await (0, utils_1.getProxyFactory)(hre, ImplFactory.signer); | ||
if (opts.initialOwner !== undefined) { | ||
throw new upgrades_core_1.InitialOwnerUnsupportedKindError(kind); | ||
} | ||
const ProxyFactory = await (0, utils_1.getProxyFactory)(hre, signer); | ||
proxyDeployment = Object.assign({ kind }, await (0, utils_1.deploy)(hre, opts, ProxyFactory, impl, data)); | ||
@@ -44,5 +55,5 @@ break; | ||
case 'transparent': { | ||
const adminAddress = await hre.upgrades.deployProxyAdmin(ImplFactory.signer, opts); | ||
const TransparentUpgradeableProxyFactory = await (0, utils_1.getTransparentUpgradeableProxyFactory)(hre, ImplFactory.signer); | ||
proxyDeployment = Object.assign({ kind }, await (0, utils_1.deploy)(hre, opts, TransparentUpgradeableProxyFactory, impl, adminAddress, data)); | ||
const initialOwner = await (0, initial_owner_1.getInitialOwner)(opts, signer); | ||
const TransparentUpgradeableProxyFactory = await (0, utils_1.getTransparentUpgradeableProxyFactory)(hre, signer); | ||
proxyDeployment = Object.assign({ kind }, await (0, utils_1.deploy)(hre, opts, TransparentUpgradeableProxyFactory, impl, initialOwner, data)); | ||
break; | ||
@@ -49,0 +60,0 @@ } |
@@ -6,4 +6,4 @@ "use strict"; | ||
const utils_1 = require("./utils"); | ||
const simulate_deploy_1 = require("./utils/simulate-deploy"); | ||
const deploy_impl_1 = require("./utils/deploy-impl"); | ||
const ethers_1 = require("./utils/ethers"); | ||
function makeForceImport(hre) { | ||
@@ -13,7 +13,7 @@ return async function forceImport(addressOrInstance, ImplFactory, opts = {}) { | ||
const manifest = await upgrades_core_1.Manifest.forNetwork(provider); | ||
const address = (0, utils_1.getContractAddress)(addressOrInstance); | ||
const address = await (0, utils_1.getContractAddress)(addressOrInstance); | ||
const implAddress = await (0, upgrades_core_1.getImplementationAddressFromProxy)(provider, address); | ||
if (implAddress !== undefined) { | ||
await importProxyToManifest(provider, hre, address, implAddress, ImplFactory, opts, manifest); | ||
return ImplFactory.attach(address); | ||
return (0, ethers_1.attach)(ImplFactory, address); | ||
} | ||
@@ -23,4 +23,4 @@ else if (await (0, upgrades_core_1.isBeacon)(provider, address)) { | ||
await addImplToManifest(hre, beaconImplAddress, ImplFactory, opts); | ||
const UpgradeableBeaconFactory = await (0, utils_1.getUpgradeableBeaconFactory)(hre, ImplFactory.signer); | ||
return UpgradeableBeaconFactory.attach(address); | ||
const UpgradeableBeaconFactory = await (0, utils_1.getUpgradeableBeaconFactory)(hre, (0, ethers_1.getSigner)(ImplFactory.runner)); | ||
return (0, ethers_1.attach)(UpgradeableBeaconFactory, address); | ||
} | ||
@@ -32,3 +32,3 @@ else { | ||
await addImplToManifest(hre, address, ImplFactory, opts); | ||
return ImplFactory.attach(address); | ||
return (0, ethers_1.attach)(ImplFactory, address); | ||
} | ||
@@ -54,3 +54,3 @@ }; | ||
if (importKind === 'transparent') { | ||
await addAdminToManifest(provider, hre, proxyAddress, ImplFactory, opts); | ||
await assertNonEmptyAdminSlot(provider, proxyAddress); | ||
} | ||
@@ -62,6 +62,11 @@ await (0, upgrades_core_1.addProxyToManifest)(importKind, proxyAddress, manifest); | ||
} | ||
async function addAdminToManifest(provider, hre, proxyAddress, ImplFactory, opts) { | ||
async function assertNonEmptyAdminSlot(provider, proxyAddress) { | ||
const adminAddress = await (0, upgrades_core_1.getAdminAddress)(provider, proxyAddress); | ||
await (0, simulate_deploy_1.simulateDeployAdmin)(hre, ImplFactory, opts, adminAddress); | ||
if ((0, upgrades_core_1.isEmptySlot)(adminAddress)) { | ||
// Assert that the admin slot of a transparent proxy is not zero, otherwise the wrong kind may be imported. | ||
// Note: Transparent proxies should not have the zero address as the admin, according to TransparentUpgradeableProxy's _setAdmin function. | ||
throw new upgrades_core_1.UpgradesError(`Proxy at ${proxyAddress} doesn't look like a transparent proxy`, () => `The proxy doesn't look like a transparent proxy because its admin address slot is empty. ` + | ||
`Set the \`kind\` option to the kind of proxy that was deployed at ${proxyAddress} (either 'uups' or 'beacon')`); | ||
} | ||
} | ||
//# sourceMappingURL=force-import.js.map |
@@ -1,4 +0,4 @@ | ||
import '@nomiclabs/hardhat-ethers'; | ||
import '@nomicfoundation/hardhat-ethers'; | ||
import './type-extensions'; | ||
import { silenceWarnings } from '@openzeppelin/upgrades-core'; | ||
import type { silenceWarnings } from '@openzeppelin/upgrades-core'; | ||
import type { DeployFunction } from './deploy-proxy'; | ||
@@ -11,10 +11,11 @@ import type { PrepareUpgradeFunction } from './prepare-upgrade'; | ||
import type { ForceImportFunction } from './force-import'; | ||
import type { ChangeAdminFunction, TransferProxyAdminOwnershipFunction, GetInstanceFunction } from './admin'; | ||
import type { ChangeAdminFunction, TransferProxyAdminOwnershipFunction } from './admin'; | ||
import type { ValidateImplementationFunction } from './validate-implementation'; | ||
import type { ValidateUpgradeFunction } from './validate-upgrade'; | ||
import type { DeployImplementationFunction } from './deploy-implementation'; | ||
import { DeployAdminFunction } from './deploy-proxy-admin'; | ||
import type { DeployContractFunction } from './deploy-contract'; | ||
import type { ProposeUpgradeFunction } from './platform/propose-upgrade'; | ||
import type { GetDefaultApprovalProcessFunction } from './platform/get-default-approval-process'; | ||
import type { ProposeUpgradeWithApprovalFunction } from './defender/propose-upgrade-with-approval'; | ||
import type { GetDeployApprovalProcessFunction, GetUpgradeApprovalProcessFunction } from './defender/get-approval-process'; | ||
import type { ProposeUpgradeFunction } from './defender-v1/propose-upgrade'; | ||
import type { VerifyDeployFunction, VerifyDeployWithUploadedArtifactFunction, GetVerifyDeployArtifactFunction, GetVerifyDeployBuildInfoFunction, GetBytecodeDigestFunction } from './defender-v1/verify-deployment'; | ||
export interface HardhatUpgrades { | ||
@@ -30,7 +31,5 @@ deployProxy: DeployFunction; | ||
upgradeBeacon: UpgradeBeaconFunction; | ||
deployProxyAdmin: DeployAdminFunction; | ||
forceImport: ForceImportFunction; | ||
silenceWarnings: typeof silenceWarnings; | ||
admin: { | ||
getInstance: GetInstanceFunction; | ||
changeProxyAdmin: ChangeAdminFunction; | ||
@@ -48,8 +47,21 @@ transferProxyAdminOwnership: TransferProxyAdminOwnershipFunction; | ||
} | ||
export interface PlatformHardhatUpgrades extends HardhatUpgrades { | ||
deployContract: DeployContractFunction; | ||
export interface DefenderV1HardhatUpgrades { | ||
proposeUpgrade: ProposeUpgradeFunction; | ||
getDefaultApprovalProcess: GetDefaultApprovalProcessFunction; | ||
verifyDeployment: VerifyDeployFunction; | ||
verifyDeploymentWithUploadedArtifact: VerifyDeployWithUploadedArtifactFunction; | ||
getDeploymentArtifact: GetVerifyDeployArtifactFunction; | ||
getDeploymentBuildInfo: GetVerifyDeployBuildInfoFunction; | ||
getBytecodeDigest: GetBytecodeDigestFunction; | ||
} | ||
export interface DefenderHardhatUpgrades extends HardhatUpgrades, DefenderV1HardhatUpgrades { | ||
deployContract: DeployContractFunction; | ||
proposeUpgradeWithApproval: ProposeUpgradeWithApprovalFunction; | ||
getDeployApprovalProcess: GetDeployApprovalProcessFunction; | ||
getUpgradeApprovalProcess: GetUpgradeApprovalProcessFunction; | ||
/** | ||
* @deprecated Use `getUpgradeApprovalProcess` instead. | ||
*/ | ||
getDefaultApprovalProcess: GetUpgradeApprovalProcessFunction; | ||
} | ||
export { UpgradeOptions } from './utils/options'; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -27,3 +27,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
require("@nomiclabs/hardhat-ethers"); | ||
require("@nomicfoundation/hardhat-ethers"); | ||
require("./type-extensions"); | ||
@@ -33,4 +33,2 @@ const config_1 = require("hardhat/config"); | ||
const plugins_1 = require("hardhat/plugins"); | ||
const upgrades_core_1 = require("@openzeppelin/upgrades-core"); | ||
const deploy_proxy_admin_1 = require("./deploy-proxy-admin"); | ||
(0, config_1.subtask)(task_names_1.TASK_COMPILE_SOLIDITY, async (args, hre, runSuper) => { | ||
@@ -52,3 +50,3 @@ const { readValidations, ValidationsCacheOutdated, ValidationsCacheNotFound } = await Promise.resolve().then(() => __importStar(require('./utils/validations'))); | ||
(0, config_1.subtask)(task_names_1.TASK_COMPILE_SOLIDITY_COMPILE, async (args, hre, runSuper) => { | ||
const { validate, solcInputOutputDecoder } = await Promise.resolve().then(() => __importStar(require('@openzeppelin/upgrades-core'))); | ||
const { isNamespaceSupported, validate, solcInputOutputDecoder, makeNamespacedInput } = await Promise.resolve().then(() => __importStar(require('@openzeppelin/upgrades-core'))); | ||
const { writeValidations } = await Promise.resolve().then(() => __importStar(require('./utils/validations'))); | ||
@@ -60,3 +58,9 @@ // TODO: patch input | ||
const decodeSrc = solcInputOutputDecoder(args.input, output); | ||
const validations = validate(output, decodeSrc, args.solcVersion); | ||
let namespacedOutput = undefined; | ||
if (isNamespaceSupported(args.solcVersion)) { | ||
const namespacedInput = makeNamespacedInput(args.input, output); | ||
namespacedOutput = (await runSuper({ ...args, quiet: true, input: namespacedInput })).output; | ||
await checkNamespacedCompileErrors(namespacedOutput); | ||
} | ||
const validations = validate(output, decodeSrc, args.solcVersion, args.input, namespacedOutput); | ||
await writeValidations(hre, validations); | ||
@@ -66,2 +70,20 @@ } | ||
}); | ||
/** | ||
* Checks for compile errors in the modified contracts for namespaced storage. | ||
* If errors are found, throws an error with the compile error messages. | ||
*/ | ||
async function checkNamespacedCompileErrors(namespacedOutput) { | ||
const errors = []; | ||
if (namespacedOutput.errors !== undefined) { | ||
for (const error of namespacedOutput.errors) { | ||
if (error.severity === 'error') { | ||
errors.push(error.formattedMessage); | ||
} | ||
} | ||
} | ||
if (errors.length > 0) { | ||
const { UpgradesError } = await Promise.resolve().then(() => __importStar(require('@openzeppelin/upgrades-core'))); | ||
throw new UpgradesError(`Failed to compile modified contracts for namespaced storage:\n\n${errors.join('\n')}`, () => 'Please report this at https://zpl.in/upgrades/report. If possible, include the source code for the contracts mentioned in the errors above.'); | ||
} | ||
} | ||
(0, config_1.extendEnvironment)(hre => { | ||
@@ -71,20 +93,40 @@ hre.upgrades = (0, plugins_1.lazyObject)(() => { | ||
}); | ||
hre.platform = (0, plugins_1.lazyObject)(() => { | ||
return makePlatformFunctions(hre); | ||
warnOnHardhatDefender(); | ||
hre.defender = (0, plugins_1.lazyObject)(() => { | ||
return makeDefenderFunctions(hre); | ||
}); | ||
}); | ||
function warnOnHardhatDefender() { | ||
if (tryRequire('@openzeppelin/hardhat-defender', true)) { | ||
const { logWarning } = require('@openzeppelin/upgrades-core'); | ||
logWarning('The @openzeppelin/hardhat-defender package is deprecated.', [ | ||
'Uninstall the @openzeppelin/hardhat-defender package.', | ||
'OpenZeppelin Defender integration is included as part of the Hardhat Upgrades plugin.', | ||
]); | ||
} | ||
} | ||
(0, config_1.extendConfig)((config) => { | ||
var _a, _b, _c; | ||
var _a, _b; | ||
// Accumulate references to all the compiler settings, including overrides | ||
const settings = []; | ||
for (const compiler of config.solidity.compilers) { | ||
compiler.settings ?? (compiler.settings = {}); | ||
(_a = compiler.settings).outputSelection ?? (_a.outputSelection = {}); | ||
(_b = compiler.settings.outputSelection)['*'] ?? (_b['*'] = {}); | ||
(_c = compiler.settings.outputSelection['*'])['*'] ?? (_c['*'] = []); | ||
if (!compiler.settings.outputSelection['*']['*'].includes('storageLayout')) { | ||
compiler.settings.outputSelection['*']['*'].push('storageLayout'); | ||
settings.push(compiler.settings); | ||
} | ||
for (const compilerOverride of Object.values(config.solidity.overrides)) { | ||
compilerOverride.settings ?? (compilerOverride.settings = {}); | ||
settings.push(compilerOverride.settings); | ||
} | ||
// Enable storage layout in all of them | ||
for (const setting of settings) { | ||
setting.outputSelection ?? (setting.outputSelection = {}); | ||
(_a = setting.outputSelection)['*'] ?? (_a['*'] = {}); | ||
(_b = setting.outputSelection['*'])['*'] ?? (_b['*'] = []); | ||
if (!setting.outputSelection['*']['*'].includes('storageLayout')) { | ||
setting.outputSelection['*']['*'].push('storageLayout'); | ||
} | ||
} | ||
}); | ||
if (tryRequire('@nomiclabs/hardhat-etherscan')) { | ||
(0, config_1.subtask)('verify:verify').setAction(async (args, hre, runSuper) => { | ||
if (tryRequire('@nomicfoundation/hardhat-verify')) { | ||
(0, config_1.subtask)('verify:etherscan').setAction(async (args, hre, runSuper) => { | ||
const { verify } = await Promise.resolve().then(() => __importStar(require('./verify-proxy'))); | ||
@@ -94,4 +136,4 @@ return await verify(args, hre, runSuper); | ||
} | ||
function makeFunctions(hre, platform) { | ||
const { silenceWarnings, getAdminAddress, getImplementationAddress, getBeaconAddress, } = require('@openzeppelin/upgrades-core'); | ||
function makeFunctions(hre, defender) { | ||
const { silenceWarnings, getAdminAddress, getImplementationAddress, getBeaconAddress, getImplementationAddressFromBeacon, } = require('@openzeppelin/upgrades-core'); | ||
const { makeDeployProxy } = require('./deploy-proxy'); | ||
@@ -107,20 +149,18 @@ const { makeUpgradeProxy } = require('./upgrade-proxy'); | ||
const { makeForceImport } = require('./force-import'); | ||
const { makeChangeProxyAdmin, makeTransferProxyAdminOwnership, makeGetInstanceFunction } = require('./admin'); | ||
const { makeChangeProxyAdmin, makeTransferProxyAdminOwnership } = require('./admin'); | ||
return { | ||
silenceWarnings, | ||
deployProxy: makeDeployProxy(hre, platform), | ||
upgradeProxy: makeUpgradeProxy(hre, platform), | ||
deployProxy: makeDeployProxy(hre, defender), | ||
upgradeProxy: makeUpgradeProxy(hre, defender), | ||
validateImplementation: makeValidateImplementation(hre), | ||
validateUpgrade: makeValidateUpgrade(hre), | ||
deployImplementation: makeDeployImplementation(hre, platform), | ||
prepareUpgrade: makePrepareUpgrade(hre, platform), | ||
deployBeacon: makeDeployBeacon(hre, platform), | ||
deployBeaconProxy: makeDeployBeaconProxy(hre, platform), | ||
upgradeBeacon: makeUpgradeBeacon(hre, platform), | ||
deployProxyAdmin: (0, deploy_proxy_admin_1.makeDeployProxyAdmin)(hre, platform), | ||
deployImplementation: makeDeployImplementation(hre, defender), | ||
prepareUpgrade: makePrepareUpgrade(hre, defender), | ||
deployBeacon: makeDeployBeacon(hre, defender), | ||
deployBeaconProxy: makeDeployBeaconProxy(hre, defender), | ||
upgradeBeacon: makeUpgradeBeacon(hre, defender), | ||
forceImport: makeForceImport(hre), | ||
admin: { | ||
getInstance: makeGetInstanceFunction(hre), | ||
changeProxyAdmin: makeChangeProxyAdmin(hre, platform), | ||
transferProxyAdminOwnership: makeTransferProxyAdminOwnership(hre, platform), // block on platform | ||
changeProxyAdmin: makeChangeProxyAdmin(hre, defender), | ||
transferProxyAdminOwnership: makeTransferProxyAdminOwnership(hre, defender), // block on defender | ||
}, | ||
@@ -133,3 +173,3 @@ erc1967: { | ||
beacon: { | ||
getImplementationAddress: (beaconAddress) => (0, upgrades_core_1.getImplementationAddressFromBeacon)(hre.network.provider, beaconAddress), | ||
getImplementationAddress: (beaconAddress) => getImplementationAddressFromBeacon(hre.network.provider, beaconAddress), | ||
}, | ||
@@ -141,16 +181,32 @@ }; | ||
} | ||
function makePlatformFunctions(hre) { | ||
function makeDefenderV1Functions(hre) { | ||
const { makeVerifyDeploy, makeVerifyDeployWithUploadedArtifact, makeGetVerifyDeployBuildInfo, makeGetVerifyDeployArtifact, makeGetBytecodeDigest, } = require('./defender-v1/verify-deployment'); | ||
const { makeProposeUpgrade } = require('./defender-v1/propose-upgrade'); | ||
return { | ||
proposeUpgrade: makeProposeUpgrade(hre), | ||
verifyDeployment: makeVerifyDeploy(hre), | ||
verifyDeploymentWithUploadedArtifact: makeVerifyDeployWithUploadedArtifact(hre), | ||
getDeploymentArtifact: makeGetVerifyDeployArtifact(hre), | ||
getDeploymentBuildInfo: makeGetVerifyDeployBuildInfo(hre), | ||
getBytecodeDigest: makeGetBytecodeDigest(hre), | ||
}; | ||
} | ||
function makeDefenderFunctions(hre) { | ||
const { makeDeployContract } = require('./deploy-contract'); | ||
const { makeProposeUpgrade } = require('./platform/propose-upgrade'); | ||
const { makeGetDefaultApprovalProcess } = require('./platform/get-default-approval-process'); | ||
const { makeProposeUpgradeWithApproval } = require('./defender/propose-upgrade-with-approval'); | ||
const { makeGetDeployApprovalProcess, makeGetUpgradeApprovalProcess } = require('./defender/get-approval-process'); | ||
const getUpgradeApprovalProcess = makeGetUpgradeApprovalProcess(hre); | ||
return { | ||
...makeFunctions(hre, true), | ||
...makeDefenderV1Functions(hre), | ||
deployContract: makeDeployContract(hre, true), | ||
proposeUpgrade: makeProposeUpgrade(hre, true), | ||
getDefaultApprovalProcess: makeGetDefaultApprovalProcess(hre), | ||
proposeUpgradeWithApproval: makeProposeUpgradeWithApproval(hre, true), | ||
getDeployApprovalProcess: makeGetDeployApprovalProcess(hre), | ||
getUpgradeApprovalProcess: getUpgradeApprovalProcess, | ||
getDefaultApprovalProcess: getUpgradeApprovalProcess, // deprecated, is an alias for getUpgradeApprovalProcess | ||
}; | ||
} | ||
function tryRequire(id) { | ||
function tryRequire(id, resolveOnly) { | ||
try { | ||
require(id); | ||
resolveOnly ? require.resolve(id) : require(id); | ||
return true; | ||
@@ -157,0 +213,0 @@ } |
@@ -7,4 +7,4 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
export type PrepareUpgradeFunction = (referenceAddressOrContract: ContractAddressOrInstance, ImplFactory: ContractFactory, opts?: PrepareUpgradeOptions) => Promise<DeployImplementationResponse>; | ||
export declare function makePrepareUpgrade(hre: HardhatRuntimeEnvironment, platformModule: boolean): PrepareUpgradeFunction; | ||
export declare function makePrepareUpgrade(hre: HardhatRuntimeEnvironment, defenderModule: boolean): PrepareUpgradeFunction; | ||
export declare function deployImplForUpgrade(hre: HardhatRuntimeEnvironment, referenceAddressOrContract: ContractAddressOrInstance, ImplFactory: ContractFactory, opts?: PrepareUpgradeOptions): Promise<DeployedImpl>; | ||
//# sourceMappingURL=prepare-upgrade.d.ts.map |
@@ -6,9 +6,9 @@ "use strict"; | ||
const upgrades_core_1 = require("@openzeppelin/upgrades-core"); | ||
const utils_2 = require("./platform/utils"); | ||
const utils_2 = require("./defender/utils"); | ||
const deploy_impl_1 = require("./utils/deploy-impl"); | ||
function makePrepareUpgrade(hre, platformModule) { | ||
function makePrepareUpgrade(hre, defenderModule) { | ||
return async function prepareUpgrade(referenceAddressOrContract, ImplFactory, opts = {}) { | ||
opts = (0, utils_2.enablePlatform)(hre, platformModule, opts); | ||
opts = (0, utils_2.enableDefender)(hre, defenderModule, opts); | ||
const deployedImpl = await deployImplForUpgrade(hre, referenceAddressOrContract, ImplFactory, opts); | ||
if (opts.getTxResponse && deployedImpl.txResponse !== undefined) { | ||
if (opts.getTxResponse && deployedImpl.txResponse) { | ||
return deployedImpl.txResponse; | ||
@@ -23,3 +23,3 @@ } | ||
async function deployImplForUpgrade(hre, referenceAddressOrContract, ImplFactory, opts = {}) { | ||
const referenceAddress = (0, utils_1.getContractAddress)(referenceAddressOrContract); | ||
const referenceAddress = await (0, utils_1.getContractAddress)(referenceAddressOrContract); | ||
const { provider } = hre.network; | ||
@@ -26,0 +26,0 @@ let deployedImpl; |
import 'hardhat/types/runtime'; | ||
import type { HardhatUpgrades, PlatformHardhatUpgrades } from '.'; | ||
import 'hardhat/types/config'; | ||
import type { HardhatUpgrades, DefenderHardhatUpgrades } from '.'; | ||
declare module 'hardhat/types/runtime' { | ||
interface HardhatRuntimeEnvironment { | ||
upgrades: HardhatUpgrades; | ||
platform: PlatformHardhatUpgrades; | ||
defender: DefenderHardhatUpgrades; | ||
} | ||
} | ||
export interface HardhatPlatformConfig { | ||
export interface HardhatDefenderConfig { | ||
apiKey: string; | ||
apiSecret: string; | ||
usePlatformDeploy?: boolean; | ||
useDefenderDeploy?: boolean; | ||
} | ||
declare module 'hardhat/types/config' { | ||
interface HardhatUserConfig { | ||
platform?: HardhatPlatformConfig; | ||
defender?: HardhatDefenderConfig; | ||
} | ||
interface HardhatConfig { | ||
platform?: HardhatPlatformConfig; | ||
defender?: HardhatDefenderConfig; | ||
} | ||
} | ||
//# sourceMappingURL=type-extensions.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
require("hardhat/types/runtime"); | ||
require("hardhat/types/config"); | ||
//# sourceMappingURL=type-extensions.js.map |
@@ -5,3 +5,3 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
export type UpgradeBeaconFunction = (beacon: ContractAddressOrInstance, ImplFactory: ContractFactory, opts?: UpgradeBeaconOptions) => Promise<Contract>; | ||
export declare function makeUpgradeBeacon(hre: HardhatRuntimeEnvironment, platformModule: boolean): UpgradeBeaconFunction; | ||
export declare function makeUpgradeBeacon(hre: HardhatRuntimeEnvironment, defenderModule: boolean): UpgradeBeaconFunction; | ||
//# sourceMappingURL=upgrade-beacon.d.ts.map |
@@ -5,11 +5,12 @@ "use strict"; | ||
const utils_1 = require("./utils"); | ||
const utils_2 = require("./platform/utils"); | ||
function makeUpgradeBeacon(hre, platformModule) { | ||
const utils_2 = require("./defender/utils"); | ||
function makeUpgradeBeacon(hre, defenderModule) { | ||
return async function upgradeBeacon(beacon, ImplFactory, opts = {}) { | ||
(0, utils_2.disablePlatform)(hre, platformModule, opts, upgradeBeacon.name); | ||
const beaconAddress = (0, utils_1.getContractAddress)(beacon); | ||
(0, utils_2.disableDefender)(hre, defenderModule, opts, upgradeBeacon.name); | ||
const beaconAddress = await (0, utils_1.getContractAddress)(beacon); | ||
const { impl: nextImpl } = await (0, utils_1.deployBeaconImpl)(hre, ImplFactory, opts, beaconAddress); | ||
const UpgradeableBeaconFactory = await (0, utils_1.getUpgradeableBeaconFactory)(hre, ImplFactory.signer); | ||
const beaconContract = UpgradeableBeaconFactory.attach(beaconAddress); | ||
const upgradeTx = await beaconContract.upgradeTo(nextImpl); | ||
const UpgradeableBeaconFactory = await (0, utils_1.getUpgradeableBeaconFactory)(hre, (0, utils_1.getSigner)(ImplFactory.runner)); | ||
const beaconContract = (0, utils_1.attach)(UpgradeableBeaconFactory, beaconAddress); | ||
const overrides = opts.txOverrides ? [opts.txOverrides] : []; | ||
const upgradeTx = await beaconContract.upgradeTo(nextImpl, ...overrides); | ||
// @ts-ignore Won't be readonly because beaconContract was created through attach. | ||
@@ -16,0 +17,0 @@ beaconContract.deployTransaction = upgradeTx; |
@@ -0,1 +1,2 @@ | ||
/// <reference types="debug" /> | ||
import { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
@@ -5,3 +6,3 @@ import type { ContractFactory, Contract } from 'ethers'; | ||
export type UpgradeFunction = (proxy: ContractAddressOrInstance, ImplFactory: ContractFactory, opts?: UpgradeProxyOptions) => Promise<Contract>; | ||
export declare function makeUpgradeProxy(hre: HardhatRuntimeEnvironment, platformModule: boolean): UpgradeFunction; | ||
export declare function makeUpgradeProxy(hre: HardhatRuntimeEnvironment, defenderModule: boolean, log?: import("debug").Debugger): UpgradeFunction; | ||
//# sourceMappingURL=upgrade-proxy.d.ts.map |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.makeUpgradeProxy = void 0; | ||
const debug_1 = __importDefault(require("./utils/debug")); | ||
const upgrades_core_1 = require("@openzeppelin/upgrades-core"); | ||
const utils_1 = require("./utils"); | ||
const utils_2 = require("./platform/utils"); | ||
function makeUpgradeProxy(hre, platformModule) { | ||
const utils_2 = require("./defender/utils"); | ||
const ethers_1 = require("./utils/ethers"); | ||
const attach_abi_1 = require("./utils/attach-abi"); | ||
function makeUpgradeProxy(hre, defenderModule, log = debug_1.default) { | ||
return async function upgradeProxy(proxy, ImplFactory, opts = {}) { | ||
(0, utils_2.disablePlatform)(hre, platformModule, opts, upgradeProxy.name); | ||
const proxyAddress = (0, utils_1.getContractAddress)(proxy); | ||
(0, utils_2.disableDefender)(hre, defenderModule, opts, upgradeProxy.name); | ||
const proxyAddress = await (0, utils_1.getContractAddress)(proxy); | ||
const { impl: nextImpl } = await (0, utils_1.deployProxyImpl)(hre, ImplFactory, opts, proxyAddress); | ||
// upgrade kind is inferred above | ||
const upgradeTo = await getUpgrader(proxyAddress, ImplFactory.signer); | ||
const upgradeTo = await getUpgrader(proxyAddress, opts, (0, utils_1.getSigner)(ImplFactory.runner)); | ||
const call = encodeCall(ImplFactory, opts.call); | ||
const upgradeTx = await upgradeTo(nextImpl, call); | ||
const inst = ImplFactory.attach(proxyAddress); | ||
const inst = (0, ethers_1.attach)(ImplFactory, proxyAddress); | ||
// @ts-ignore Won't be readonly because inst was created through attach. | ||
@@ -21,22 +27,46 @@ inst.deployTransaction = upgradeTx; | ||
}; | ||
async function getUpgrader(proxyAddress, signer) { | ||
async function getUpgrader(proxyAddress, opts, signer) { | ||
const { provider } = hre.network; | ||
const adminAddress = await (0, upgrades_core_1.getAdminAddress)(provider, proxyAddress); | ||
const adminBytecode = await (0, upgrades_core_1.getCode)(provider, adminAddress); | ||
const overrides = opts.txOverrides ? [opts.txOverrides] : []; | ||
if ((0, upgrades_core_1.isEmptySlot)(adminAddress) || adminBytecode === '0x') { | ||
// No admin contract: use ITransparentUpgradeableProxyFactory to get proxiable interface | ||
const ITransparentUpgradeableProxyFactory = await (0, utils_1.getITransparentUpgradeableProxyFactory)(hre, signer); | ||
const proxy = ITransparentUpgradeableProxyFactory.attach(proxyAddress); | ||
return (nextImpl, call) => (call ? proxy.upgradeToAndCall(nextImpl, call) : proxy.upgradeTo(nextImpl)); | ||
// No admin contract: use ITransparentUpgradeableProxy to get proxiable interface | ||
const upgradeInterfaceVersion = await (0, upgrades_core_1.getUpgradeInterfaceVersion)(provider, proxyAddress, log); | ||
switch (upgradeInterfaceVersion) { | ||
case '5.0.0': { | ||
const proxy = await (0, attach_abi_1.attachITransparentUpgradeableProxyV5)(hre, proxyAddress, signer); | ||
return (nextImpl, call) => proxy.upgradeToAndCall(nextImpl, call ?? '0x', ...overrides); | ||
} | ||
default: { | ||
if (upgradeInterfaceVersion !== undefined) { | ||
// Log as debug if the interface version is an unknown string. | ||
// Do not throw an error because this could be caused by a fallback function. | ||
log(`Unknown UPGRADE_INTERFACE_VERSION ${upgradeInterfaceVersion} for proxy at ${proxyAddress}. Expected 5.0.0`); | ||
} | ||
const proxy = await (0, attach_abi_1.attachITransparentUpgradeableProxyV4)(hre, proxyAddress, signer); | ||
return (nextImpl, call) => call ? proxy.upgradeToAndCall(nextImpl, call, ...overrides) : proxy.upgradeTo(nextImpl, ...overrides); | ||
} | ||
} | ||
} | ||
else { | ||
// Admin contract: redirect upgrade call through it | ||
const manifest = await upgrades_core_1.Manifest.forNetwork(provider); | ||
const AdminFactory = await (0, utils_1.getProxyAdminFactory)(hre, signer); | ||
const admin = AdminFactory.attach(adminAddress); | ||
const manifestAdmin = await manifest.getAdmin(); | ||
if (admin.address !== manifestAdmin?.address) { | ||
throw new Error('Proxy admin is not the one registered in the network manifest'); | ||
const upgradeInterfaceVersion = await (0, upgrades_core_1.getUpgradeInterfaceVersion)(provider, adminAddress, log); | ||
switch (upgradeInterfaceVersion) { | ||
case '5.0.0': { | ||
const admin = await (0, attach_abi_1.attachProxyAdminV5)(hre, adminAddress, signer); | ||
return (nextImpl, call) => admin.upgradeAndCall(proxyAddress, nextImpl, call ?? '0x', ...overrides); | ||
} | ||
default: { | ||
if (upgradeInterfaceVersion !== undefined) { | ||
// Log as debug if the interface version is an unknown string. | ||
// Do not throw an error because this could be caused by a fallback function. | ||
log(`Unknown UPGRADE_INTERFACE_VERSION ${upgradeInterfaceVersion} for proxy admin at ${adminAddress}. Expected 5.0.0`); | ||
} | ||
const admin = await (0, attach_abi_1.attachProxyAdminV4)(hre, adminAddress, signer); | ||
return (nextImpl, call) => call | ||
? admin.upgradeAndCall(proxyAddress, nextImpl, call, ...overrides) | ||
: admin.upgrade(proxyAddress, nextImpl, ...overrides); | ||
} | ||
} | ||
return (nextImpl, call) => call ? admin.upgradeAndCall(proxyAddress, nextImpl, call) : admin.upgrade(proxyAddress, nextImpl); | ||
} | ||
@@ -43,0 +73,0 @@ } |
import { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
import type { ContractFactory } from 'ethers'; | ||
import { DeployTransaction, Platform } from '.'; | ||
import { DeployTransaction, DefenderDeploy } from '.'; | ||
import { Deployment, RemoteDeploymentId, DeployOpts } from '@openzeppelin/upgrades-core'; | ||
/** | ||
* Gets a contract instance from a deployment, where the deployment may be remote. | ||
* If the deployment is remote, the instance have an overriden `deployed` method to wait for the remote deployment | ||
* and update its `deployTransaction` with the new transaction hash if it was detected to have changed. | ||
* If the deployment is remote, the instance has an overriden `waitForDeployment` method to wait for the remote deployment | ||
* and update its `deploymentTransaction` with the new transaction hash if it was detected to have changed. | ||
* | ||
* @param hre The Hardhat Runtime Environment | ||
* @param contract The contract factory | ||
* @param opts The deploy and platform options | ||
* @param opts The deploy and defender options | ||
* @param deployment The deployment | ||
@@ -17,3 +17,3 @@ * @param deployTransaction The transaction that deployed the contract, if available | ||
*/ | ||
export declare function getContractInstance(hre: HardhatRuntimeEnvironment, contract: ContractFactory, opts: DeployOpts & Platform, deployment: Deployment & DeployTransaction & RemoteDeploymentId): import("ethers").Contract; | ||
export declare function getContractInstance(hre: HardhatRuntimeEnvironment, contract: ContractFactory, opts: DeployOpts & DefenderDeploy, deployment: Deployment & DeployTransaction & RemoteDeploymentId): import("ethers").Contract; | ||
//# sourceMappingURL=contract-instance.d.ts.map |
@@ -8,11 +8,12 @@ "use strict"; | ||
const assert_1 = __importDefault(require("assert")); | ||
const utils_1 = require("../platform/utils"); | ||
const utils_1 = require("../defender/utils"); | ||
const ethers_1 = require("./ethers"); | ||
/** | ||
* Gets a contract instance from a deployment, where the deployment may be remote. | ||
* If the deployment is remote, the instance have an overriden `deployed` method to wait for the remote deployment | ||
* and update its `deployTransaction` with the new transaction hash if it was detected to have changed. | ||
* If the deployment is remote, the instance has an overriden `waitForDeployment` method to wait for the remote deployment | ||
* and update its `deploymentTransaction` with the new transaction hash if it was detected to have changed. | ||
* | ||
* @param hre The Hardhat Runtime Environment | ||
* @param contract The contract factory | ||
* @param opts The deploy and platform options | ||
* @param opts The deploy and defender options | ||
* @param deployment The deployment | ||
@@ -23,15 +24,16 @@ * @param deployTransaction The transaction that deployed the contract, if available | ||
function getContractInstance(hre, contract, opts, deployment) { | ||
const instance = contract.attach(deployment.address); | ||
const instance = (0, ethers_1.attach)(contract, deployment.address); | ||
// @ts-ignore Won't be readonly because instance was created through attach. | ||
instance.deployTransaction = deployment.deployTransaction; | ||
if (opts.usePlatformDeploy && deployment.remoteDeploymentId !== undefined) { | ||
const origDeployed = instance.deployed.bind(instance); | ||
instance.deployed = async () => { | ||
instance.deploymentTransaction = () => deployment.deployTransaction ?? null; // Convert undefined to null to conform to ethers.js types. | ||
if (opts.useDefenderDeploy && deployment.remoteDeploymentId !== undefined) { | ||
const origWait = instance.waitForDeployment.bind(instance); | ||
instance.waitForDeployment = async () => { | ||
(0, assert_1.default)(deployment.remoteDeploymentId !== undefined); | ||
const updatedTxHash = await (0, utils_1.waitForDeployment)(hre, opts, instance.address, deployment.remoteDeploymentId); | ||
const updatedTxHash = await (0, utils_1.waitForDeployment)(hre, opts, await instance.getAddress(), deployment.remoteDeploymentId); | ||
if (updatedTxHash !== undefined && updatedTxHash !== deployment.txHash) { | ||
const updatedTx = await hre.ethers.provider.getTransaction(updatedTxHash); | ||
// @ts-ignore Won't be readonly because instance was created through attach. | ||
instance.deployTransaction = await hre.ethers.provider.getTransaction(updatedTxHash); | ||
instance.deploymentTransaction = () => updatedTx; | ||
} | ||
return await origDeployed(); | ||
return await origWait(); | ||
}; | ||
@@ -38,0 +40,0 @@ } |
export type ContractAddressOrInstance = string | { | ||
address: string; | ||
getAddress(): Promise<string>; | ||
}; | ||
export declare function getContractAddress(addressOrInstance: ContractAddressOrInstance): string; | ||
export declare function getContractAddress(addressOrInstance: ContractAddressOrInstance): Promise<string>; | ||
//# sourceMappingURL=contract-types.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getContractAddress = void 0; | ||
function getContractAddress(addressOrInstance) { | ||
async function getContractAddress(addressOrInstance) { | ||
if (typeof addressOrInstance === 'string') { | ||
@@ -9,3 +9,3 @@ return addressOrInstance; | ||
else { | ||
return addressOrInstance.address; | ||
return await addressOrInstance.getAddress(); | ||
} | ||
@@ -12,0 +12,0 @@ } |
@@ -7,3 +7,3 @@ import { StorageLayout, ValidationDataCurrent, ValidationOptions, Version } from '@openzeppelin/upgrades-core'; | ||
impl: string; | ||
txResponse?: ethers.providers.TransactionResponse; | ||
txResponse?: ethers.TransactionResponse; | ||
} | ||
@@ -10,0 +10,0 @@ export interface DeployedProxyImpl extends DeployedImpl { |
@@ -5,6 +5,5 @@ "use strict"; | ||
const upgrades_core_1 = require("@openzeppelin/upgrades-core"); | ||
const utils_1 = require("ethers/lib/utils"); | ||
const deploy_1 = require("./deploy"); | ||
const options_1 = require("./options"); | ||
const utils_2 = require("../platform/utils"); | ||
const utils_1 = require("../defender/utils"); | ||
const validate_impl_1 = require("./validate-impl"); | ||
@@ -54,3 +53,3 @@ const validations_1 = require("./validations"); | ||
const deployment = await (0, upgrades_core_1.fetchOrDeployGetDeployment)(deployData.version, deployData.provider, async () => { | ||
const abi = ImplFactory.interface.format(utils_1.FormatTypes.minimal); | ||
const abi = ImplFactory.interface.format(true); | ||
const attemptDeploy = () => { | ||
@@ -73,10 +72,10 @@ if (deployData.fullOpts.useDeployedImplementation || deployData.fullOpts.redeployImplementation === 'never') { | ||
return { ...deployment, layout }; | ||
}, opts, merge, remoteDeploymentId => (0, utils_2.getRemoteDeployment)(hre, remoteDeploymentId)); | ||
}, opts, merge, remoteDeploymentId => (0, utils_1.getRemoteDeployment)(hre, remoteDeploymentId)); | ||
let txResponse; | ||
if (opts.getTxResponse) { | ||
if ('deployTransaction' in deployment) { | ||
txResponse = deployment.deployTransaction; | ||
txResponse = deployment.deployTransaction ?? undefined; | ||
} | ||
else if (deployment.txHash !== undefined) { | ||
txResponse = await hre.ethers.provider.getTransaction(deployment.txHash); | ||
txResponse = (await hre.ethers.provider.getTransaction(deployment.txHash)) ?? undefined; | ||
} | ||
@@ -83,0 +82,0 @@ } |
import type { Deployment, RemoteDeploymentId } from '@openzeppelin/upgrades-core'; | ||
import type { ethers, ContractFactory } from 'ethers'; | ||
import { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
import { PlatformDeployOptions, UpgradeOptions } from './options'; | ||
import { EthersDeployOptions, DefenderDeployOptions, UpgradeOptions } from './options'; | ||
export interface DeployTransaction { | ||
deployTransaction: ethers.providers.TransactionResponse; | ||
deployTransaction?: ethers.TransactionResponse; | ||
} | ||
export declare function deploy(hre: HardhatRuntimeEnvironment, opts: UpgradeOptions & PlatformDeployOptions, factory: ContractFactory, ...args: unknown[]): Promise<Required<Deployment & DeployTransaction> & RemoteDeploymentId>; | ||
export declare function deploy(hre: HardhatRuntimeEnvironment, opts: UpgradeOptions & EthersDeployOptions & DefenderDeployOptions, factory: ContractFactory, ...args: unknown[]): Promise<Required<Deployment> & DeployTransaction & RemoteDeploymentId>; | ||
//# sourceMappingURL=deploy.d.ts.map |
"use strict"; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.deploy = void 0; | ||
const debug_1 = __importDefault(require("./debug")); | ||
const utils_1 = require("ethers/lib/utils"); | ||
const deploy_1 = require("../platform/deploy"); | ||
const deploy_1 = require("../defender/deploy"); | ||
async function deploy(hre, opts, factory, ...args) { | ||
if (opts?.usePlatformDeploy) { | ||
return await (0, deploy_1.platformDeploy)(hre, factory, opts, ...args); | ||
// defender always includes RemoteDeploymentId, while ethers always includes DeployTransaction | ||
if (opts?.useDefenderDeploy) { | ||
return await (0, deploy_1.defenderDeploy)(hre, factory, opts, ...args); | ||
} | ||
else { | ||
if (opts.txOverrides !== undefined) { | ||
args.push(opts.txOverrides); | ||
} | ||
return await ethersDeploy(factory, ...args); | ||
@@ -21,10 +20,7 @@ } | ||
const contractInstance = await factory.deploy(...args); | ||
const { deployTransaction } = contractInstance; | ||
const address = (0, utils_1.getContractAddress)({ | ||
from: await factory.signer.getAddress(), | ||
nonce: deployTransaction.nonce, | ||
}); | ||
if (address !== contractInstance.address) { | ||
(0, debug_1.default)(`overriding contract address from ${contractInstance.address} to ${address} for nonce ${deployTransaction.nonce}`); | ||
const deployTransaction = contractInstance.deploymentTransaction(); | ||
if (deployTransaction === null) { | ||
throw new Error('Broken invariant: deploymentTransaction is null'); | ||
} | ||
const address = await contractInstance.getAddress(); | ||
const txHash = deployTransaction.hash; | ||
@@ -31,0 +27,0 @@ return { address, txHash, deployTransaction }; |
import { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
import { EtherscanNetworkEntry } from '@nomiclabs/hardhat-etherscan/dist/src/types'; | ||
import { Etherscan } from '@nomicfoundation/hardhat-verify/etherscan'; | ||
/** | ||
* Call the configured Etherscan API with the given parameters. | ||
* | ||
* @param etherscanApi The Etherscan API config | ||
* @param etherscan Etherscan instance | ||
* @param params The API parameters to call with | ||
* @returns The Etherscan API response | ||
*/ | ||
export declare function callEtherscanApi(etherscanApi: EtherscanAPIConfig, params: any): Promise<EtherscanResponseBody>; | ||
export declare function callEtherscanApi(etherscan: Etherscan, params: any): Promise<EtherscanResponseBody>; | ||
/** | ||
* Gets the Etherscan API parameters from Hardhat config. | ||
* Makes use of Hardhat Etherscan for handling cases when Etherscan API parameters are not present in config. | ||
* Gets an Etherscan instance based on Hardhat config. | ||
* Throws an error if Etherscan API key is not present in config. | ||
*/ | ||
export declare function getEtherscanAPIConfig(hre: HardhatRuntimeEnvironment): Promise<EtherscanAPIConfig>; | ||
export declare function getEtherscanInstance(hre: HardhatRuntimeEnvironment): Promise<Etherscan>; | ||
/** | ||
* The Etherscan API parameters from the Hardhat config. | ||
*/ | ||
export interface EtherscanAPIConfig { | ||
key: string; | ||
endpoints: EtherscanNetworkEntry; | ||
} | ||
/** | ||
* The response body from an Etherscan API call. | ||
@@ -29,6 +22,21 @@ */ | ||
message: string; | ||
result: any; | ||
result: unknown; | ||
} | ||
export declare const RESPONSE_OK = "1"; | ||
export declare function verifyAndGetStatus(params: { | ||
contractAddress: string; | ||
sourceCode: string; | ||
contractName: string; | ||
compilerVersion: string; | ||
constructorArguments: string; | ||
}, etherscan: Etherscan): Promise<{ | ||
readonly status: number; | ||
readonly message: string; | ||
isPending(): boolean; | ||
isFailure(): boolean; | ||
isSuccess(): boolean; | ||
isBytecodeMissingInNetworkError(): boolean; | ||
isOk(): boolean; | ||
}>; | ||
export {}; | ||
//# sourceMappingURL=etherscan-api.d.ts.map |
@@ -6,20 +6,19 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.RESPONSE_OK = exports.getEtherscanAPIConfig = exports.callEtherscanApi = void 0; | ||
const resolveEtherscanApiKey_1 = require("@nomiclabs/hardhat-etherscan/dist/src/resolveEtherscanApiKey"); | ||
exports.verifyAndGetStatus = exports.RESPONSE_OK = exports.getEtherscanInstance = exports.callEtherscanApi = void 0; | ||
const upgrades_core_1 = require("@openzeppelin/upgrades-core"); | ||
const undici_1 = require("undici"); | ||
const debug_1 = __importDefault(require("./debug")); | ||
const etherscan_1 = require("@nomicfoundation/hardhat-verify/etherscan"); | ||
/** | ||
* Call the configured Etherscan API with the given parameters. | ||
* | ||
* @param etherscanApi The Etherscan API config | ||
* @param etherscan Etherscan instance | ||
* @param params The API parameters to call with | ||
* @returns The Etherscan API response | ||
*/ | ||
async function callEtherscanApi(etherscanApi, params) { | ||
const parameters = new URLSearchParams({ ...params, apikey: etherscanApi.key }); | ||
const response = await (0, undici_1.request)(etherscanApi.endpoints.urls.apiURL, { | ||
async function callEtherscanApi(etherscan, params) { | ||
const parameters = { ...params, apikey: etherscan.apiKey }; | ||
const response = await (0, undici_1.request)(etherscan.apiUrl, { | ||
method: 'POST', | ||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, | ||
body: parameters.toString(), | ||
query: parameters, | ||
}); | ||
@@ -36,13 +35,17 @@ if (!(response.statusCode >= 200 && response.statusCode <= 299)) { | ||
/** | ||
* Gets the Etherscan API parameters from Hardhat config. | ||
* Makes use of Hardhat Etherscan for handling cases when Etherscan API parameters are not present in config. | ||
* Gets an Etherscan instance based on Hardhat config. | ||
* Throws an error if Etherscan API key is not present in config. | ||
*/ | ||
async function getEtherscanAPIConfig(hre) { | ||
const endpoints = await hre.run('verify:get-etherscan-endpoint'); | ||
const etherscanConfig = hre.config.etherscan; | ||
const key = (0, resolveEtherscanApiKey_1.resolveEtherscanApiKey)(etherscanConfig.apiKey, endpoints.network); | ||
return { key, endpoints }; | ||
async function getEtherscanInstance(hre) { | ||
const etherscanConfig = hre.config.etherscan; // This should never be undefined, but check just in case | ||
const chainConfig = await etherscan_1.Etherscan.getCurrentChainConfig(hre.network.name, hre.network.provider, etherscanConfig?.customChains ?? []); | ||
return etherscan_1.Etherscan.fromChainConfig(etherscanConfig?.apiKey, chainConfig); | ||
} | ||
exports.getEtherscanAPIConfig = getEtherscanAPIConfig; | ||
exports.getEtherscanInstance = getEtherscanInstance; | ||
exports.RESPONSE_OK = '1'; | ||
async function verifyAndGetStatus(params, etherscan) { | ||
const response = await etherscan.verify(params.contractAddress, params.sourceCode, params.contractName, params.compilerVersion, params.constructorArguments); | ||
return etherscan.getVerificationStatus(response.message); | ||
} | ||
exports.verifyAndGetStatus = verifyAndGetStatus; | ||
//# sourceMappingURL=etherscan-api.js.map |
@@ -0,9 +1,7 @@ | ||
import { ContractFactory, Signer } from 'ethers'; | ||
import { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
import { Signer, ContractFactory } from 'ethers'; | ||
export declare function getProxyFactory(hre: HardhatRuntimeEnvironment, signer?: Signer): Promise<ContractFactory>; | ||
export declare function getTransparentUpgradeableProxyFactory(hre: HardhatRuntimeEnvironment, signer?: Signer): Promise<ContractFactory>; | ||
export declare function getITransparentUpgradeableProxyFactory(hre: HardhatRuntimeEnvironment, signer?: Signer): Promise<ContractFactory>; | ||
export declare function getProxyAdminFactory(hre: HardhatRuntimeEnvironment, signer?: Signer): Promise<ContractFactory>; | ||
export declare function getBeaconProxyFactory(hre: HardhatRuntimeEnvironment, signer?: Signer): Promise<ContractFactory>; | ||
export declare function getUpgradeableBeaconFactory(hre: HardhatRuntimeEnvironment, signer?: Signer): Promise<ContractFactory>; | ||
//# sourceMappingURL=factories.d.ts.map |
@@ -6,9 +6,7 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getUpgradeableBeaconFactory = exports.getBeaconProxyFactory = exports.getProxyAdminFactory = exports.getITransparentUpgradeableProxyFactory = exports.getTransparentUpgradeableProxyFactory = exports.getProxyFactory = void 0; | ||
const ERC1967Proxy_json_1 = __importDefault(require("@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json")); | ||
const BeaconProxy_json_1 = __importDefault(require("@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol/BeaconProxy.json")); | ||
const UpgradeableBeacon_json_1 = __importDefault(require("@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json")); | ||
const TransparentUpgradeableProxy_json_1 = __importDefault(require("@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json")); | ||
const ITransparentUpgradeableProxy_json_1 = __importDefault(require("@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/ITransparentUpgradeableProxy.json")); | ||
const ProxyAdmin_json_1 = __importDefault(require("@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json")); | ||
exports.getUpgradeableBeaconFactory = exports.getBeaconProxyFactory = exports.getTransparentUpgradeableProxyFactory = exports.getProxyFactory = void 0; | ||
const ERC1967Proxy_json_1 = __importDefault(require("@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json")); | ||
const BeaconProxy_json_1 = __importDefault(require("@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol/BeaconProxy.json")); | ||
const UpgradeableBeacon_json_1 = __importDefault(require("@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json")); | ||
const TransparentUpgradeableProxy_json_1 = __importDefault(require("@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json")); | ||
async function getProxyFactory(hre, signer) { | ||
@@ -22,10 +20,2 @@ return hre.ethers.getContractFactory(ERC1967Proxy_json_1.default.abi, ERC1967Proxy_json_1.default.bytecode, signer); | ||
exports.getTransparentUpgradeableProxyFactory = getTransparentUpgradeableProxyFactory; | ||
async function getITransparentUpgradeableProxyFactory(hre, signer) { | ||
return hre.ethers.getContractFactory(ITransparentUpgradeableProxy_json_1.default.abi, ITransparentUpgradeableProxy_json_1.default.bytecode, signer); | ||
} | ||
exports.getITransparentUpgradeableProxyFactory = getITransparentUpgradeableProxyFactory; | ||
async function getProxyAdminFactory(hre, signer) { | ||
return hre.ethers.getContractFactory(ProxyAdmin_json_1.default.abi, ProxyAdmin_json_1.default.bytecode, signer); | ||
} | ||
exports.getProxyAdminFactory = getProxyAdminFactory; | ||
async function getBeaconProxyFactory(hre, signer) { | ||
@@ -32,0 +22,0 @@ return hre.ethers.getContractFactory(BeaconProxy_json_1.default.abi, BeaconProxy_json_1.default.bytecode, signer); |
@@ -10,2 +10,4 @@ export * from './deploy'; | ||
export * from './initializer-data'; | ||
export { attach, getSigner } from './ethers'; | ||
export { attachITransparentUpgradeableProxyV4, attachITransparentUpgradeableProxyV5, attachProxyAdminV4, attachProxyAdminV5, } from './attach-abi'; | ||
//# sourceMappingURL=index.d.ts.map |
@@ -17,3 +17,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.simulateDeployImpl = exports.deployBeaconImpl = exports.deployProxyImpl = void 0; | ||
exports.attachProxyAdminV5 = exports.attachProxyAdminV4 = exports.attachITransparentUpgradeableProxyV5 = exports.attachITransparentUpgradeableProxyV4 = exports.getSigner = exports.attach = exports.simulateDeployImpl = exports.deployBeaconImpl = exports.deployProxyImpl = void 0; | ||
__exportStar(require("./deploy"), exports); | ||
@@ -31,2 +31,10 @@ var deploy_impl_1 = require("./deploy-impl"); | ||
__exportStar(require("./initializer-data"), exports); | ||
var ethers_1 = require("./ethers"); | ||
Object.defineProperty(exports, "attach", { enumerable: true, get: function () { return ethers_1.attach; } }); | ||
Object.defineProperty(exports, "getSigner", { enumerable: true, get: function () { return ethers_1.getSigner; } }); | ||
var attach_abi_1 = require("./attach-abi"); | ||
Object.defineProperty(exports, "attachITransparentUpgradeableProxyV4", { enumerable: true, get: function () { return attach_abi_1.attachITransparentUpgradeableProxyV4; } }); | ||
Object.defineProperty(exports, "attachITransparentUpgradeableProxyV5", { enumerable: true, get: function () { return attach_abi_1.attachITransparentUpgradeableProxyV5; } }); | ||
Object.defineProperty(exports, "attachProxyAdminV4", { enumerable: true, get: function () { return attach_abi_1.attachProxyAdminV4; } }); | ||
Object.defineProperty(exports, "attachProxyAdminV5", { enumerable: true, get: function () { return attach_abi_1.attachProxyAdminV5; } }); | ||
//# sourceMappingURL=index.js.map |
@@ -1,3 +0,3 @@ | ||
import { Interface } from '@ethersproject/abi'; | ||
import { Interface } from 'ethers'; | ||
export declare function getInitializerData(contractInterface: Interface, args: unknown[], initializer?: string | false): string; | ||
//# sourceMappingURL=initializer-data.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.getInitializerData = void 0; | ||
const upgrades_core_1 = require("@openzeppelin/upgrades-core"); | ||
function getInitializerData(contractInterface, args, initializer) { | ||
@@ -10,16 +11,16 @@ if (initializer === false) { | ||
initializer = initializer ?? 'initialize'; | ||
try { | ||
const fragment = contractInterface.getFunction(initializer); | ||
const fragment = contractInterface.getFunction(initializer); | ||
if (fragment === null) { | ||
if (allowNoInitialization) { | ||
return '0x'; | ||
} | ||
else { | ||
throw new upgrades_core_1.UpgradesError(`The contract has no initializer function matching the name or signature: ${initializer}`, () => `Ensure that the initializer function exists, specify an existing function with the 'initializer' option, or set the 'initializer' option to false to omit the initializer call.`); | ||
} | ||
} | ||
else { | ||
return contractInterface.encodeFunctionData(fragment, args); | ||
} | ||
catch (e) { | ||
if (e instanceof Error) { | ||
if (allowNoInitialization && e.message.includes('no matching function')) { | ||
return '0x'; | ||
} | ||
} | ||
throw e; | ||
} | ||
} | ||
exports.getInitializerData = getInitializerData; | ||
//# sourceMappingURL=initializer-data.js.map |
import { DeployOpts, ProxyKindOption, StandaloneValidationOptions, ValidationOptions } from '@openzeppelin/upgrades-core'; | ||
import { Overrides } from 'ethers'; | ||
/** | ||
* Options for functions that can deploy an implementation contract. | ||
*/ | ||
export type StandaloneOptions = StandaloneValidationOptions & DeployOpts & { | ||
export type StandaloneOptions = StandaloneValidationOptions & DeployOpts & EthersDeployOptions & { | ||
constructorArgs?: unknown[]; | ||
@@ -28,26 +29,39 @@ /** | ||
/** | ||
* Option to enable or disable Platform deployments. | ||
* Option to enable or disable Defender deployments. | ||
*/ | ||
export type Platform = { | ||
usePlatformDeploy?: boolean; | ||
export type DefenderDeploy = { | ||
useDefenderDeploy?: boolean; | ||
}; | ||
/** | ||
* Options for functions that support Platform deployments. | ||
* Options for functions that support Defender deployments. | ||
*/ | ||
export type PlatformDeployOptions = Platform & { | ||
export type DefenderDeployOptions = DefenderDeploy & { | ||
verifySourceCode?: boolean; | ||
relayerId?: string; | ||
salt?: string; | ||
createFactoryAddress?: string; | ||
}; | ||
export type DeployBeaconProxyOptions = DeployOpts & ProxyKindOption & Initializer & PlatformDeployOptions; | ||
export type DeployBeaconOptions = StandaloneOptions & Platform; | ||
export type DeployImplementationOptions = StandaloneOptions & GetTxResponse & PlatformDeployOptions; | ||
export type DeployContractOptions = StandaloneOptions & GetTxResponse & PlatformDeployOptions & { | ||
/** | ||
* Options for functions that support deployments through ethers.js. | ||
*/ | ||
export type EthersDeployOptions = { | ||
/** | ||
* Overrides for the transaction sent to deploy a contract. | ||
*/ | ||
txOverrides?: Overrides; | ||
}; | ||
export type InitialOwner = { | ||
initialOwner?: string; | ||
}; | ||
export type DeployBeaconProxyOptions = EthersDeployOptions & DeployOpts & ProxyKindOption & Initializer & DefenderDeployOptions; | ||
export type DeployBeaconOptions = StandaloneOptions & InitialOwner & DefenderDeploy; | ||
export type DeployImplementationOptions = StandaloneOptions & GetTxResponse & DefenderDeployOptions; | ||
export type DeployContractOptions = Omit<StandaloneOptions, 'txOverrides'> & // ethers deployment not supported for deployContract | ||
GetTxResponse & DefenderDeployOptions & { | ||
unsafeAllowDeployContract?: boolean; | ||
}; | ||
export type DeployProxyAdminOptions = DeployOpts & Platform; | ||
export type DeployProxyOptions = StandaloneOptions & Initializer & PlatformDeployOptions; | ||
export type DeployProxyOptions = StandaloneOptions & Initializer & InitialOwner & DefenderDeployOptions; | ||
export type ForceImportOptions = ProxyKindOption; | ||
export type PrepareUpgradeOptions = UpgradeOptions & GetTxResponse & PlatformDeployOptions; | ||
export type UpgradeBeaconOptions = UpgradeOptions & Platform; | ||
export type PrepareUpgradeOptions = UpgradeOptions & GetTxResponse & DefenderDeployOptions; | ||
export type UpgradeBeaconOptions = UpgradeOptions & DefenderDeploy; | ||
export type UpgradeProxyOptions = UpgradeOptions & { | ||
@@ -58,3 +72,3 @@ call?: { | ||
} | string; | ||
} & Platform; | ||
} & DefenderDeploy; | ||
export type ValidateImplementationOptions = StandaloneValidationOptions; | ||
@@ -61,0 +75,0 @@ export type ValidateUpgradeOptions = ValidationOptions; |
@@ -12,2 +12,3 @@ "use strict"; | ||
redeployImplementation: opts.redeployImplementation ?? 'onchange', | ||
txOverrides: opts.txOverrides ?? {}, | ||
...(0, upgrades_core_1.withValidationDefaults)(opts), | ||
@@ -14,0 +15,0 @@ }; |
import type { ContractFactory } from 'ethers'; | ||
import type { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
import { UpgradeOptions } from './options'; | ||
export declare function simulateDeployAdmin(hre: HardhatRuntimeEnvironment, ProxyAdminFactory: ContractFactory, opts: UpgradeOptions, adminAddress: string): Promise<void>; | ||
export declare function simulateDeployImpl(hre: HardhatRuntimeEnvironment, ImplFactory: ContractFactory, opts: UpgradeOptions, implAddress: string): Promise<void>; | ||
//# sourceMappingURL=simulate-deploy.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.simulateDeployImpl = exports.simulateDeployAdmin = void 0; | ||
exports.simulateDeployImpl = void 0; | ||
const upgrades_core_1 = require("@openzeppelin/upgrades-core"); | ||
const utils_1 = require("ethers/lib/utils"); | ||
const deploy_impl_1 = require("./deploy-impl"); | ||
@@ -11,13 +10,2 @@ // To import an already deployed contract we want to reuse fetchOrDeploy for its ability to validate | ||
// actually deploying something. | ||
async function simulateDeployAdmin(hre, ProxyAdminFactory, opts, adminAddress) { | ||
const { deployData, simulateDeploy } = await getSimulatedData(hre, ProxyAdminFactory, opts, adminAddress); | ||
const manifestAdminAddress = await (0, upgrades_core_1.fetchOrDeployAdmin)(deployData.provider, simulateDeploy, opts); | ||
if (adminAddress !== manifestAdminAddress) { | ||
(0, upgrades_core_1.logWarning)(`Imported proxy with admin at '${adminAddress}' which differs from previously deployed admin '${manifestAdminAddress}'`, [ | ||
`The imported proxy admin is different from the proxy admin that was previously deployed on this network. This proxy will not be upgradable directly by the plugin.`, | ||
`To upgrade this proxy, use the prepareUpgrade or defender.proposeUpgrade function and then upgrade it using the admin at '${adminAddress}'.`, | ||
]); | ||
} | ||
} | ||
exports.simulateDeployAdmin = simulateDeployAdmin; | ||
async function simulateDeployImpl(hre, ImplFactory, opts, implAddress) { | ||
@@ -35,3 +23,3 @@ const { deployData, simulateDeploy } = await getSimulatedData(hre, ImplFactory, opts, implAddress); | ||
return { | ||
abi: ImplFactory.interface.format(utils_1.FormatTypes.minimal), | ||
abi: ImplFactory.interface.format(true), | ||
layout: deployData.layout, | ||
@@ -38,0 +26,0 @@ address: implAddress, |
@@ -23,3 +23,3 @@ "use strict"; | ||
else { | ||
const referenceAddress = (0, utils_1.getContractAddress)(referenceAddressOrImplFactory); | ||
const referenceAddress = await (0, utils_1.getContractAddress)(referenceAddressOrImplFactory); | ||
const { provider } = hre.network; | ||
@@ -26,0 +26,0 @@ const deployData = await (0, deploy_impl_1.getDeployData)(hre, newImplFactory, opts); |
import { HardhatRuntimeEnvironment, RunSuperFunction } from 'hardhat/types'; | ||
/** | ||
* Overrides hardhat-etherscan's verify:verify subtask to fully verify a proxy or beacon. | ||
* Overrides hardhat-verify's verify:etherscan subtask 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-etherscan's verify function directly. | ||
* as well as the implementation. Otherwise, calls hardhat-verify's verify function directly. | ||
* | ||
* @param args Args to the hardhat-etherscan verify function | ||
* @param args Args to the hardhat-verify verify function | ||
* @param hre | ||
* @param runSuper The parent function which is expected to be hardhat-etherscan's verify function | ||
* @param runSuper The parent function which is expected to be hardhat-verify's verify function | ||
* @returns | ||
@@ -12,0 +12,0 @@ */ |
@@ -7,14 +7,13 @@ "use strict"; | ||
exports.verify = void 0; | ||
const EtherscanVerifyContractRequest_1 = require("@nomiclabs/hardhat-etherscan/dist/src/etherscan/EtherscanVerifyContractRequest"); | ||
const EtherscanService_1 = require("@nomiclabs/hardhat-etherscan/dist/src/etherscan/EtherscanService"); | ||
const upgrades_core_1 = require("@openzeppelin/upgrades-core"); | ||
const build_info_json_1 = __importDefault(require("@openzeppelin/upgrades-core/artifacts/build-info.json")); | ||
const ERC1967Proxy_json_1 = __importDefault(require("@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json")); | ||
const BeaconProxy_json_1 = __importDefault(require("@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol/BeaconProxy.json")); | ||
const UpgradeableBeacon_json_1 = __importDefault(require("@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json")); | ||
const TransparentUpgradeableProxy_json_1 = __importDefault(require("@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json")); | ||
const ProxyAdmin_json_1 = __importDefault(require("@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json")); | ||
const build_info_v5_json_1 = __importDefault(require("@openzeppelin/upgrades-core/artifacts/build-info-v5.json")); | ||
const ERC1967Proxy_json_1 = __importDefault(require("@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json")); | ||
const BeaconProxy_json_1 = __importDefault(require("@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol/BeaconProxy.json")); | ||
const UpgradeableBeacon_json_1 = __importDefault(require("@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json")); | ||
const TransparentUpgradeableProxy_json_1 = __importDefault(require("@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json")); | ||
const ProxyAdmin_json_1 = __importDefault(require("@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json")); | ||
const ethereumjs_util_1 = require("ethereumjs-util"); | ||
const debug_1 = __importDefault(require("./utils/debug")); | ||
const etherscan_api_1 = require("./utils/etherscan-api"); | ||
const etherscan_api_2 = require("./utils/etherscan-api"); | ||
/** | ||
@@ -31,10 +30,10 @@ * The proxy-related contracts and their corresponding events that may have been deployed the current version of this plugin. | ||
/** | ||
* Overrides hardhat-etherscan's verify:verify subtask to fully verify a proxy or beacon. | ||
* Overrides hardhat-verify's verify:etherscan subtask 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-etherscan's verify function directly. | ||
* as well as the implementation. Otherwise, calls hardhat-verify's verify function directly. | ||
* | ||
* @param args Args to the hardhat-etherscan verify function | ||
* @param args Args to the hardhat-verify verify function | ||
* @param hre | ||
* @param runSuper The parent function which is expected to be hardhat-etherscan's verify function | ||
* @param runSuper The parent function which is expected to be hardhat-verify's verify function | ||
* @returns | ||
@@ -44,7 +43,7 @@ */ | ||
if (!runSuper.isDefined) { | ||
throw new upgrades_core_1.UpgradesError('The hardhat-etherscan plugin must be imported before the hardhat-upgrades plugin.', () => 'Import the plugins in the following order in hardhat.config.js:\n' + | ||
' require("@nomiclabs/hardhat-etherscan");\n' + | ||
throw new upgrades_core_1.UpgradesError('The hardhat-verify plugin must be imported before the hardhat-upgrades plugin.', () => 'Import the plugins in the following order in hardhat.config.js:\n' + | ||
' require("@nomicfoundation/hardhat-verify");\n' + | ||
' require("@openzeppelin/hardhat-upgrades");\n' + | ||
'Or if you are using TypeScript, import the plugins in the following order in hardhat.config.ts:\n' + | ||
' import "@nomiclabs/hardhat-etherscan";\n' + | ||
' import "@nomicfoundation/hardhat-verify";\n' + | ||
' import "@openzeppelin/hardhat-upgrades";\n'); | ||
@@ -67,4 +66,4 @@ } | ||
proxy = false; | ||
const etherscanApi = await (0, etherscan_api_1.getEtherscanAPIConfig)(hre); | ||
await fullVerifyBeacon(hre, proxyAddress, hardhatVerify, etherscanApi, errorReport); | ||
const etherscan = await (0, etherscan_api_1.getEtherscanInstance)(hre); | ||
await fullVerifyBeacon(hre, proxyAddress, hardhatVerify, etherscan, errorReport); | ||
} | ||
@@ -122,6 +121,11 @@ else { | ||
/** | ||
* Indicates that the expected event topic was not found in the contract's logs according to the Etherscan API. | ||
* Indicates that the expected event topic was not found in the contract's logs according to the Etherscan API, or an expected function was not found. | ||
*/ | ||
class EventNotFound extends upgrades_core_1.UpgradesError { | ||
class EventOrFunctionNotFound extends upgrades_core_1.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.'); | ||
} | ||
} | ||
/** | ||
@@ -148,3 +152,3 @@ * Indicates that the contract's bytecode does not match with the plugin's artifact. | ||
* @param proxyAddress The transparent or UUPS proxy address | ||
* @param hardhatVerify A function that invokes the hardhat-etherscan plugin's verify command | ||
* @param hardhatVerify A function that invokes the hardhat-verify plugin's verify command | ||
* @param errorReport Accumulated verification errors | ||
@@ -156,5 +160,5 @@ */ | ||
await verifyImplementation(hardhatVerify, implAddress, errorReport); | ||
const etherscanApi = await (0, etherscan_api_1.getEtherscanAPIConfig)(hre); | ||
const etherscan = await (0, etherscan_api_1.getEtherscanInstance)(hre); | ||
await verifyTransparentOrUUPS(); | ||
await linkProxyWithImplementationAbi(etherscanApi, proxyAddress, implAddress, errorReport); | ||
await linkProxyWithImplementationAbi(etherscan, proxyAddress, implAddress, errorReport); | ||
// Either UUPS or Transparent proxy could have admin slot set, although typically this should only be for Transparent | ||
@@ -166,18 +170,41 @@ await verifyAdmin(); | ||
console.log(`Verifying proxy admin: ${adminAddress}`); | ||
try { | ||
await verifyWithArtifactOrFallback(hre, hardhatVerify, etherscanApi, adminAddress, [verifiableContracts.proxyAdmin], errorReport, | ||
// The user provided the proxy address to verify, whereas this function is only verifying the related proxy admin. | ||
// So even if this falls back and succeeds, we want to keep any errors that might have occurred while verifying the proxy itself. | ||
false); | ||
await verifyAdminOrFallback(hardhatVerify, etherscan, adminAddress, errorReport); | ||
} | ||
} | ||
/** | ||
* Verifies a proxy admin contract by looking up an OwnershipTransferred event that should have been logged during construction | ||
* to get the owner used for its constructor. | ||
* | ||
* This is different from the verifyWithArtifactOrFallback function because the proxy admin in Contracts 5.0 is not deployed directly by the plugin, | ||
* but is deployed by the transparent proxy itself, so we cannot infer the admin's constructor arguments from the originating transaction's input bytecode. | ||
*/ | ||
async function verifyAdminOrFallback(hardhatVerify, etherscan, adminAddress, errorReport) { | ||
const attemptVerify = async () => { | ||
let encodedOwner; | ||
// Get the OwnershipTransferred event when the ProxyAdmin was created, which should have the encoded owner address as its second parameter (third topic). | ||
const response = await getEventResponse(adminAddress, verifiableContracts.proxyAdmin.event, etherscan); | ||
if (response === undefined) { | ||
throw new EventsNotFound(adminAddress, [verifiableContracts.proxyAdmin.event]); | ||
} | ||
catch (e) { | ||
if (e instanceof EventNotFound) { | ||
console.log('Verification skipped for proxy admin - the admin address does not appear to contain a ProxyAdmin contract.'); | ||
} | ||
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 deployedBytecode = await (0, upgrades_core_1.getCode)(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, | ||
// The user provided the proxy address to verify, whereas this function is only verifying the related proxy admin. | ||
// So even if this falls back and succeeds, we want to keep any errors that might have occurred while verifying the proxy itself. | ||
false); | ||
} | ||
async function verifyTransparentOrUUPS() { | ||
console.log(`Verifying proxy: ${proxyAddress}`); | ||
await verifyWithArtifactOrFallback(hre, hardhatVerify, etherscanApi, proxyAddress, [verifiableContracts.transparentUpgradeableProxy, verifiableContracts.erc1967proxy], errorReport, true); | ||
await verifyWithArtifactOrFallback(hre, hardhatVerify, etherscan, proxyAddress, [verifiableContracts.transparentUpgradeableProxy, verifiableContracts.erc1967proxy], errorReport, true); | ||
} | ||
@@ -191,3 +218,3 @@ } | ||
* @param proxyAddress The beacon proxy address | ||
* @param hardhatVerify A function that invokes the hardhat-etherscan plugin's verify command | ||
* @param hardhatVerify A function that invokes the hardhat-verify plugin's verify command | ||
* @param errorReport Accumulated verification errors | ||
@@ -199,9 +226,9 @@ */ | ||
const implAddress = await (0, upgrades_core_1.getImplementationAddressFromBeacon)(provider, beaconAddress); | ||
const etherscanApi = await (0, etherscan_api_1.getEtherscanAPIConfig)(hre); | ||
await fullVerifyBeacon(hre, beaconAddress, hardhatVerify, etherscanApi, errorReport); | ||
const etherscan = await (0, etherscan_api_1.getEtherscanInstance)(hre); | ||
await fullVerifyBeacon(hre, beaconAddress, hardhatVerify, etherscan, errorReport); | ||
await verifyBeaconProxy(); | ||
await linkProxyWithImplementationAbi(etherscanApi, proxyAddress, implAddress, errorReport); | ||
await linkProxyWithImplementationAbi(etherscan, proxyAddress, implAddress, errorReport); | ||
async function verifyBeaconProxy() { | ||
console.log(`Verifying beacon proxy: ${proxyAddress}`); | ||
await verifyWithArtifactOrFallback(hre, hardhatVerify, etherscanApi, proxyAddress, [verifiableContracts.beaconProxy], errorReport, true); | ||
await verifyWithArtifactOrFallback(hre, hardhatVerify, etherscan, proxyAddress, [verifiableContracts.beaconProxy], errorReport, true); | ||
} | ||
@@ -214,7 +241,7 @@ } | ||
* @param beaconAddress The beacon address | ||
* @param hardhatVerify A function that invokes the hardhat-etherscan plugin's verify command | ||
* @param etherscanApi Configuration for the Etherscan API | ||
* @param hardhatVerify A function that invokes the hardhat-verify plugin's verify command | ||
* @param etherscan Etherscan instance | ||
* @param errorReport Accumulated verification errors | ||
*/ | ||
async function fullVerifyBeacon(hre, beaconAddress, hardhatVerify, etherscanApi, errorReport) { | ||
async function fullVerifyBeacon(hre, beaconAddress, hardhatVerify, etherscan, errorReport) { | ||
const provider = hre.network.provider; | ||
@@ -226,9 +253,9 @@ const implAddress = await (0, upgrades_core_1.getImplementationAddressFromBeacon)(provider, beaconAddress); | ||
console.log(`Verifying beacon or beacon-like contract: ${beaconAddress}`); | ||
await verifyWithArtifactOrFallback(hre, hardhatVerify, etherscanApi, beaconAddress, [verifiableContracts.upgradeableBeacon], errorReport, true); | ||
await verifyWithArtifactOrFallback(hre, hardhatVerify, etherscan, beaconAddress, [verifiableContracts.upgradeableBeacon], errorReport, true); | ||
} | ||
} | ||
/** | ||
* Runs hardhat-etherscan plugin's verify command on the given implementation address. | ||
* Runs hardhat-verify plugin's verify command on the given implementation address. | ||
* | ||
* @param hardhatVerify A function that invokes the hardhat-etherscan plugin's verify command | ||
* @param hardhatVerify A function that invokes the hardhat-verify plugin's verify command | ||
* @param implAddress The implementation address | ||
@@ -255,3 +282,3 @@ * @param errorReport Accumulated verification errors | ||
* | ||
* @param etherscanApi The Etherscan API config | ||
* @param etherscan Etherscan instance | ||
* @param address The contract address for which to look for events | ||
@@ -261,8 +288,8 @@ * @param possibleContractInfo An array of possible contract artifacts to use for verification along | ||
* @returns the VerifiableContractInfo and txHash for the first event found | ||
* @throws {EventNotFound} if none of the events were found in the contract's logs according to Etherscan. | ||
* @throws {EventOrFunctionNotFound} if none of the events were found in the contract's logs according to Etherscan. | ||
*/ | ||
async function searchEvent(etherscanApi, address, possibleContractInfo) { | ||
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, etherscanApi); | ||
const txHash = await getContractCreationTxHash(address, contractInfo.event, etherscan); | ||
if (txHash !== undefined) { | ||
@@ -275,29 +302,26 @@ return { contractInfo, txHash }; | ||
}); | ||
throw new EventNotFound(`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.'); | ||
throw new EventsNotFound(address, events); | ||
} | ||
/** | ||
* Verifies a contract by matching with known artifacts. | ||
* Verifies a contract using the attemptVerify function. If it fails, falls back to verify directly using the regular hardhat verify task. | ||
* | ||
* If a match was not found, falls back to verify directly using the regular hardhat verify task. | ||
* | ||
* If the fallback passes, logs as success. | ||
* If the fallback also fails, records errors for both the original and fallback attempts. | ||
* | ||
* @param hre | ||
* @param etherscanApi The Etherscan API config | ||
* @param attemptVerify A function that attempts to verify the contract. | ||
* Should throw EventOrFunctionNotFound if the contract does not contain an expected event in its logs or function in its bytecode, | ||
* or BytecodeNotMatchArtifact if the contract's bytecode does not match with the plugin's known artifact. | ||
* @param hardhatVerify A function that invokes the hardhat-verify plugin's verify command | ||
* @param address The contract address to verify | ||
* @param possibleContractInfo An array of possible contract artifacts to use for verification along | ||
* with the corresponding creation event expected in the logs. | ||
* @param errorReport Accumulated verification errors | ||
* @param convertErrorsToWarningsOnFallbackSuccess If fallback verification occurred and succeeded, whether any | ||
* previously accumulated errors should be converted into warnings in the final summary. | ||
* @throws {EventNotFound} if none of the events were found in the contract's logs according to Etherscan. | ||
*/ | ||
async function verifyWithArtifactOrFallback(hre, hardhatVerify, etherscanApi, address, possibleContractInfo, errorReport, convertErrorsToWarningsOnFallbackSuccess) { | ||
async function attemptVerifyOrFallback(attemptVerify, hardhatVerify, address, errorReport, convertErrorsToWarningsOnFallbackSuccess) { | ||
try { | ||
await attemptVerifyWithCreationEvent(hre, etherscanApi, address, possibleContractInfo, errorReport); | ||
await attemptVerify(); | ||
return true; | ||
} | ||
catch (origError) { | ||
if (origError instanceof BytecodeNotMatchArtifact || origError instanceof EventNotFound) { | ||
if (origError instanceof BytecodeNotMatchArtifact || origError instanceof EventOrFunctionNotFound) { | ||
// Try falling back to regular hardhat verify in case the source code is available in the user's project. | ||
@@ -336,2 +360,23 @@ try { | ||
/** | ||
* Verifies a contract by matching with known artifacts. | ||
* | ||
* If a match was not found, falls back to verify directly using the regular hardhat verify task. | ||
* | ||
* If the fallback passes, logs as success. | ||
* If the fallback also fails, records errors for both the original and fallback attempts. | ||
* | ||
* @param hre | ||
* @param etherscan Etherscan instance | ||
* @param address The contract address to verify | ||
* @param possibleContractInfo An array of possible contract artifacts to use for verification along | ||
* with the corresponding creation event expected in the logs. | ||
* @param errorReport Accumulated verification errors | ||
* @param convertErrorsToWarningsOnFallbackSuccess If fallback verification occurred and succeeded, whether any | ||
* previously accumulated errors should be converted into warnings in the final summary. | ||
*/ | ||
async function verifyWithArtifactOrFallback(hre, hardhatVerify, etherscan, address, possibleContractInfo, errorReport, convertErrorsToWarningsOnFallbackSuccess) { | ||
const attemptVerify = () => attemptVerifyWithCreationEvent(hre, etherscan, address, possibleContractInfo, errorReport); | ||
return await attemptVerifyOrFallback(attemptVerify, hardhatVerify, address, errorReport, convertErrorsToWarningsOnFallbackSuccess); | ||
} | ||
/** | ||
* Attempts to verify a contract by looking up an event that should have been logged during contract construction, | ||
@@ -343,3 +388,3 @@ * finds the txHash for that, and infers the constructor args to use for verification. | ||
* @param hre | ||
* @param etherscanApi The Etherscan API config | ||
* @param etherscan Etherscan instance | ||
* @param address The contract address to verify | ||
@@ -349,7 +394,7 @@ * @param possibleContractInfo An array of possible contract artifacts to use for verification along | ||
* @param errorReport Accumulated verification errors | ||
* @throws {EventNotFound} if none of the events were found in the contract's logs according to Etherscan. | ||
* @throws {EventOrFunctionNotFound} if none of the events were found in the contract's logs according to Etherscan. | ||
* @throws {BytecodeNotMatchArtifact} if the contract's bytecode does not match with the plugin's known artifact. | ||
*/ | ||
async function attemptVerifyWithCreationEvent(hre, etherscanApi, address, possibleContractInfo, errorReport) { | ||
const { contractInfo, txHash } = await searchEvent(etherscanApi, address, possibleContractInfo); | ||
async function attemptVerifyWithCreationEvent(hre, etherscan, address, possibleContractInfo, errorReport) { | ||
const { contractInfo, txHash } = await searchEvent(etherscan, address, possibleContractInfo); | ||
(0, debug_1.default)(`verifying contract ${contractInfo.artifact.contractName} at ${address}`); | ||
@@ -368,3 +413,3 @@ const tx = await (0, upgrades_core_1.getTransactionByHash)(hre.network.provider, txHash); | ||
else { | ||
await verifyContractWithConstructorArgs(etherscanApi, address, contractInfo.artifact, constructorArguments, errorReport); | ||
await verifyContractWithConstructorArgs(etherscan, address, contractInfo.artifact, constructorArguments, errorReport); | ||
} | ||
@@ -375,3 +420,3 @@ } | ||
* | ||
* @param etherscanApi The Etherscan API config | ||
* @param etherscan Etherscan instance | ||
* @param address The address of the contract to verify | ||
@@ -381,22 +426,14 @@ * @param artifact The contract artifact to use for verification. | ||
*/ | ||
async function verifyContractWithConstructorArgs(etherscanApi, address, artifact, constructorArguments, errorReport) { | ||
async function verifyContractWithConstructorArgs(etherscan, address, artifact, constructorArguments, errorReport) { | ||
(0, debug_1.default)(`verifying contract ${address} with constructor args ${constructorArguments}`); | ||
const params = { | ||
apiKey: etherscanApi.key, | ||
contractAddress: address, | ||
sourceCode: JSON.stringify(build_info_json_1.default.input), | ||
sourceName: artifact.sourceName, | ||
contractName: artifact.contractName, | ||
compilerVersion: `v${build_info_json_1.default.solcLongVersion}`, | ||
sourceCode: JSON.stringify(build_info_v5_json_1.default.input), | ||
contractName: `${artifact.sourceName}:${artifact.contractName}`, | ||
compilerVersion: `v${build_info_v5_json_1.default.solcLongVersion}`, | ||
constructorArguments: constructorArguments, | ||
}; | ||
const request = (0, EtherscanVerifyContractRequest_1.toVerifyRequest)(params); | ||
try { | ||
const response = await (0, EtherscanService_1.verifyContract)(etherscanApi.endpoints.urls.apiURL, request); | ||
const statusRequest = (0, EtherscanVerifyContractRequest_1.toCheckStatusRequest)({ | ||
apiKey: etherscanApi.key, | ||
guid: response.message, | ||
}); | ||
const status = await (0, EtherscanService_1.getVerificationStatus)(etherscanApi.endpoints.urls.apiURL, statusRequest); | ||
if (status.isVerificationSuccess()) { | ||
const status = await (0, etherscan_api_2.verifyAndGetStatus)(params, etherscan); | ||
if (status.isSuccess()) { | ||
console.log(`Successfully verified contract ${artifact.contractName} at ${address}.`); | ||
@@ -418,13 +455,13 @@ } | ||
/** | ||
* 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. | ||
* Calls the Etherscan API to look for an event that should have been emitted during construction | ||
* of the contract at the given address, and returns the result corresponding to the first event found. | ||
* | ||
* @param address The address to get the creation txhash for. | ||
* @param address The address for which to get the event response. | ||
* @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 | ||
* @param etherscan Etherscan instance | ||
* @returns The event response, or undefined if not found or if | ||
* the address is not a contract. | ||
* @throws {UpgradesError} if the Etherscan API returned with not OK status | ||
*/ | ||
async function getContractCreationTxHash(address, topic, etherscanApi) { | ||
async function getEventResponse(address, topic, etherscan) { | ||
const params = { | ||
@@ -438,6 +475,6 @@ module: 'logs', | ||
}; | ||
const responseBody = await (0, etherscan_api_1.callEtherscanApi)(etherscanApi, params); | ||
const responseBody = await (0, etherscan_api_1.callEtherscanApi)(etherscan, params); | ||
if (responseBody.status === etherscan_api_1.RESPONSE_OK) { | ||
const result = responseBody.result; | ||
return result[0].transactionHash; // get the txhash from the first instance of this event | ||
return result[0]; | ||
} | ||
@@ -453,9 +490,29 @@ else if (responseBody.message === 'No records found' || responseBody.message === 'No logs found') { | ||
/** | ||
* 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 etherscan Etherscan instance | ||
* @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 | ||
*/ | ||
async function getContractCreationTxHash(address, topic, etherscan) { | ||
const eventResponse = await getEventResponse(address, topic, etherscan); | ||
if (eventResponse === undefined) { | ||
return undefined; | ||
} | ||
else { | ||
return eventResponse.transactionHash; | ||
} | ||
} | ||
/** | ||
* Calls the Etherscan API to link a proxy with its implementation ABI. | ||
* | ||
* @param etherscanApi The Etherscan API config | ||
* @param etherscan Etherscan instance | ||
* @param proxyAddress The proxy address | ||
* @param implAddress The implementation address | ||
*/ | ||
async function linkProxyWithImplementationAbi(etherscanApi, proxyAddress, implAddress, errorReport) { | ||
async function linkProxyWithImplementationAbi(etherscan, proxyAddress, implAddress, errorReport) { | ||
console.log(`Linking proxy ${proxyAddress} with implementation`); | ||
@@ -468,10 +525,10 @@ const params = { | ||
}; | ||
let responseBody = await (0, etherscan_api_1.callEtherscanApi)(etherscanApi, params); | ||
let responseBody = await (0, etherscan_api_1.callEtherscanApi)(etherscan, params); | ||
if (responseBody.status === etherscan_api_1.RESPONSE_OK) { | ||
// initial call was OK, but need to send a status request using the returned guid to get the actual verification status | ||
const guid = responseBody.result; | ||
responseBody = await checkProxyVerificationStatus(etherscanApi, guid); | ||
responseBody = await checkProxyVerificationStatus(etherscan, guid); | ||
while (responseBody.result === 'Pending in queue') { | ||
await delay(3000); | ||
responseBody = await checkProxyVerificationStatus(etherscanApi, guid); | ||
responseBody = await checkProxyVerificationStatus(etherscan, guid); | ||
} | ||
@@ -489,10 +546,10 @@ } | ||
} | ||
async function checkProxyVerificationStatus(etherscanApi, guid) { | ||
async function checkProxyVerificationStatus(etherscan, guid) { | ||
const checkProxyVerificationParams = { | ||
module: 'contract', | ||
action: 'checkproxyverification', | ||
apikey: etherscanApi.key, | ||
apikey: etherscan.apiKey, | ||
guid: guid, | ||
}; | ||
return await (0, etherscan_api_1.callEtherscanApi)(etherscanApi, checkProxyVerificationParams); | ||
return await (0, etherscan_api_1.callEtherscanApi)(etherscan, checkProxyVerificationParams); | ||
} | ||
@@ -499,0 +556,0 @@ /** |
{ | ||
"name": "@openzeppelin/hardhat-upgrades", | ||
"version": "1.28.0", | ||
"version": "3.0.2", | ||
"description": "", | ||
@@ -24,33 +24,39 @@ "repository": "https://github.com/OpenZeppelin/openzeppelin-upgrades/tree/master/packages/plugin-hardhat", | ||
"devDependencies": { | ||
"@nomiclabs/hardhat-ethers": "^2.0.0", | ||
"@nomiclabs/hardhat-etherscan": "^3.1.0", | ||
"@openzeppelin/contracts": "4.8.3", | ||
"@openzeppelin/contracts-upgradeable": "4.8.3", | ||
"@nomicfoundation/hardhat-ethers": "^3.0.5", | ||
"@nomicfoundation/hardhat-verify": "^2.0.0", | ||
"@openzeppelin/contracts": "5.0.1", | ||
"@openzeppelin/contracts-upgradeable": "5.0.1", | ||
"@types/mocha": "^7.0.2", | ||
"ava": "^5.0.0", | ||
"fgbg": "^0.1.4", | ||
"hardhat": "^2.0.2", | ||
"hardhat": "^2.19.1", | ||
"promisified": "^0.5.0", | ||
"rimraf": "^3.0.2" | ||
"proxyquire": "^2.1.3", | ||
"rimraf": "^5.0.0", | ||
"sinon": "^17.0.0" | ||
}, | ||
"dependencies": { | ||
"@openzeppelin/defender-base-client": "^1.46.0", | ||
"@openzeppelin/platform-deploy-client": "^0.8.0", | ||
"@openzeppelin/upgrades-core": "^1.27.0", | ||
"@openzeppelin/defender-admin-client": "^1.52.0", | ||
"@openzeppelin/defender-base-client": "^1.52.0", | ||
"@openzeppelin/defender-sdk-base-client": "^1.8.0", | ||
"@openzeppelin/defender-sdk-deploy-client": "^1.8.0", | ||
"@openzeppelin/upgrades-core": "^1.32.0", | ||
"chalk": "^4.1.0", | ||
"debug": "^4.1.1", | ||
"proper-lockfile": "^4.1.1" | ||
"ethereumjs-util": "^7.1.5", | ||
"proper-lockfile": "^4.1.1", | ||
"undici": "^5.28.2" | ||
}, | ||
"peerDependencies": { | ||
"@nomiclabs/hardhat-ethers": "^2.0.0", | ||
"@nomiclabs/hardhat-etherscan": "^3.1.0", | ||
"ethers": "^5.0.5", | ||
"@nomicfoundation/hardhat-ethers": "^3.0.0", | ||
"@nomicfoundation/hardhat-verify": "^2.0.0", | ||
"ethers": "^6.6.0", | ||
"hardhat": "^2.0.2" | ||
}, | ||
"peerDependenciesMeta": { | ||
"@nomiclabs/harhdat-etherscan": { | ||
"@nomicfoundation/hardhat-verify": { | ||
"optional": true | ||
} | ||
}, | ||
"gitHead": "58f7978f68fea6ba9fe7dea5364fb9412e177071" | ||
"gitHead": "25dbafa315416a1a95f39793256476bd8131731c" | ||
} |
@@ -12,3 +12,3 @@ # OpenZeppelin Hardhat Upgrades | ||
npm install --save-dev @openzeppelin/hardhat-upgrades | ||
npm install --save-dev @nomiclabs/hardhat-ethers ethers # peer dependencies | ||
npm install --save-dev @nomicfoundation/hardhat-ethers ethers # peer dependencies | ||
``` | ||
@@ -39,4 +39,4 @@ | ||
const box = await upgrades.deployProxy(Box, [42]); | ||
await box.deployed(); | ||
console.log("Box deployed to:", box.address); | ||
await box.waitForDeployment(); | ||
console.log("Box deployed to:", await box.getAddress()); | ||
} | ||
@@ -47,3 +47,3 @@ | ||
This will automatically check that the `Box` contract is upgrade-safe, set up a proxy admin (if needed), deploy an implementation contract for the `Box` contract (unless there is one already from a previous deployment), create a proxy, and initialize it by calling `initialize(42)`. | ||
This will automatically check that the `Box` contract is upgrade-safe, deploy an implementation contract for the `Box` contract (unless there is one already from a previous deployment), create a proxy (along with a proxy admin if needed), and initialize it by calling `initialize(42)`. | ||
@@ -81,8 +81,8 @@ Then, in another script, you can use the `upgradeProxy` function to upgrade the deployed instance to a new version. The new version can be a different contract (such as `BoxV2`), or you can just modify the existing `Box` contract and recompile it - the plugin will note it changed. | ||
const beacon = await upgrades.deployBeacon(Box); | ||
await beacon.deployed(); | ||
console.log("Beacon deployed to:", beacon.address); | ||
await beacon.waitForDeployment(); | ||
console.log("Beacon deployed to:", await beacon.getAddress()); | ||
const box = await upgrades.deployBeaconProxy(beacon, Box, [42]); | ||
await box.deployed(); | ||
console.log("Box deployed to:", box.address); | ||
await box.waitForDeployment(); | ||
console.log("Box deployed to:", await box.getAddress()); | ||
} | ||
@@ -126,3 +126,3 @@ | ||
const instance = await upgrades.deployProxy(Box, [42]); | ||
const upgraded = await upgrades.upgradeProxy(instance.address, BoxV2); | ||
const upgraded = await upgrades.upgradeProxy(await instance.getAddress(), BoxV2); | ||
@@ -149,3 +149,3 @@ const value = await upgraded.value(); | ||
await upgrades.upgradeBeacon(beacon, BoxV2); | ||
const upgraded = BoxV2.attach(instance.address); | ||
const upgraded = BoxV2.attach(await instance.getAddress()); | ||
@@ -152,0 +152,0 @@ const value = await upgraded.value(); |
@@ -5,4 +5,4 @@ import chalk from 'chalk'; | ||
import { Contract, Signer } from 'ethers'; | ||
import { getProxyAdminFactory } from './utils'; | ||
import { disablePlatform } from './platform/utils'; | ||
import { EthersDeployOptions, attachProxyAdminV4 } from './utils'; | ||
import { disableDefender } from './defender/utils'; | ||
@@ -12,18 +12,31 @@ const SUCCESS_CHECK = chalk.green('✔') + ' '; | ||
export type ChangeAdminFunction = (proxyAddress: string, newAdmin: string, signer?: Signer) => Promise<void>; | ||
export type TransferProxyAdminOwnershipFunction = (newOwner: string, signer?: Signer) => Promise<void>; | ||
export type ChangeAdminFunction = ( | ||
proxyAddress: string, | ||
newAdmin: string, | ||
signer?: Signer, | ||
opts?: EthersDeployOptions, | ||
) => Promise<void>; | ||
export type TransferProxyAdminOwnershipFunction = ( | ||
proxyAddress: string, | ||
newOwner: string, | ||
signer?: Signer, | ||
opts?: EthersDeployOptions, | ||
) => Promise<void>; | ||
export type GetInstanceFunction = (signer?: Signer) => Promise<Contract>; | ||
export function makeChangeProxyAdmin(hre: HardhatRuntimeEnvironment, platformModule: boolean): ChangeAdminFunction { | ||
return async function changeProxyAdmin(proxyAddress, newAdmin, signer?: Signer) { | ||
disablePlatform(hre, platformModule, {}, changeProxyAdmin.name); | ||
export function makeChangeProxyAdmin(hre: HardhatRuntimeEnvironment, defenderModule: boolean): ChangeAdminFunction { | ||
return async function changeProxyAdmin( | ||
proxyAddress: string, | ||
newAdmin: string, | ||
signer?: Signer, | ||
opts: EthersDeployOptions = {}, | ||
) { | ||
disableDefender(hre, defenderModule, {}, changeProxyAdmin.name); | ||
const admin = await getManifestAdmin(hre, signer); | ||
const proxyAdminAddress = await getAdminAddress(hre.network.provider, proxyAddress); | ||
// Only compatible with v4 admins | ||
const admin = await attachProxyAdminV4(hre, proxyAdminAddress, signer); | ||
if (admin.address !== proxyAdminAddress) { | ||
throw new Error('Proxy admin is not the one registered in the network manifest'); | ||
} else if (admin.address !== newAdmin) { | ||
await admin.changeProxyAdmin(proxyAddress, newAdmin); | ||
} | ||
const overrides = opts.txOverrides ? [opts.txOverrides] : []; | ||
await admin.changeProxyAdmin(proxyAddress, newAdmin, ...overrides); | ||
}; | ||
@@ -34,10 +47,19 @@ } | ||
hre: HardhatRuntimeEnvironment, | ||
platformModule: boolean, | ||
defenderModule: boolean, | ||
): TransferProxyAdminOwnershipFunction { | ||
return async function transferProxyAdminOwnership(newOwner, signer?: Signer) { | ||
disablePlatform(hre, platformModule, {}, transferProxyAdminOwnership.name); | ||
return async function transferProxyAdminOwnership( | ||
proxyAddress: string, | ||
newOwner: string, | ||
signer?: Signer, | ||
opts: EthersDeployOptions = {}, | ||
) { | ||
disableDefender(hre, defenderModule, {}, transferProxyAdminOwnership.name); | ||
const admin = await getManifestAdmin(hre, signer); | ||
await admin.transferOwnership(newOwner); | ||
const proxyAdminAddress = await getAdminAddress(hre.network.provider, proxyAddress); | ||
// Compatible with both v4 and v5 admins since they both have transferOwnership | ||
const admin = await attachProxyAdminV4(hre, proxyAdminAddress, signer); | ||
const overrides = opts.txOverrides ? [opts.txOverrides] : []; | ||
await admin.transferOwnership(newOwner, ...overrides); | ||
const { provider } = hre.network; | ||
@@ -47,6 +69,6 @@ const manifest = await Manifest.forNetwork(provider); | ||
for (const { address, kind } of proxies) { | ||
if (admin.address == (await getAdminAddress(provider, address))) { | ||
console.log(SUCCESS_CHECK + `${address} (${kind}) proxy ownership transfered through admin proxy`); | ||
if ((await admin.getAddress()) == (await getAdminAddress(provider, address))) { | ||
console.log(SUCCESS_CHECK + `${address} (${kind}) proxy ownership transferred through proxy admin`); | ||
} else { | ||
console.log(FAILURE_CROSS + `${address} (${kind}) proxy ownership not affected by admin proxy`); | ||
console.log(FAILURE_CROSS + `${address} (${kind}) proxy ownership not affected by proxy admin`); | ||
} | ||
@@ -56,20 +78,1 @@ } | ||
} | ||
export function makeGetInstanceFunction(hre: HardhatRuntimeEnvironment): GetInstanceFunction { | ||
return async function getInstance(signer?: Signer) { | ||
return await getManifestAdmin(hre, signer); | ||
}; | ||
} | ||
export async function getManifestAdmin(hre: HardhatRuntimeEnvironment, signer?: Signer): Promise<Contract> { | ||
const manifest = await Manifest.forNetwork(hre.network.provider); | ||
const manifestAdmin = await manifest.getAdmin(); | ||
const proxyAdminAddress = manifestAdmin?.address; | ||
if (proxyAdminAddress === undefined) { | ||
throw new Error('No ProxyAdmin was found in the network manifest'); | ||
} | ||
const AdminFactory = await getProxyAdminFactory(hre, signer); | ||
return AdminFactory.attach(proxyAdminAddress); | ||
} |
@@ -23,4 +23,5 @@ import type { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
getInitializerData, | ||
getSigner, | ||
} from './utils'; | ||
import { enablePlatform } from './platform/utils'; | ||
import { enableDefender } from './defender/utils'; | ||
import { getContractInstance } from './utils/contract-instance'; | ||
@@ -40,3 +41,3 @@ | ||
hre: HardhatRuntimeEnvironment, | ||
platformModule: boolean, | ||
defenderModule: boolean, | ||
): DeployBeaconProxyFunction { | ||
@@ -60,3 +61,3 @@ return async function deployBeaconProxy( | ||
opts = enablePlatform(hre, platformModule, opts); | ||
opts = enableDefender(hre, defenderModule, opts); | ||
@@ -71,3 +72,3 @@ const { provider } = hre.network; | ||
const beaconAddress = getContractAddress(beacon); | ||
const beaconAddress = await getContractAddress(beacon); | ||
if (!(await isBeacon(provider, beaconAddress))) { | ||
@@ -86,4 +87,4 @@ throw new DeployBeaconProxyUnsupportedError(beaconAddress); | ||
const BeaconProxyFactory = await getBeaconProxyFactory(hre, attachTo.signer); | ||
const proxyDeployment: Required<ProxyDeployment & DeployTransaction> & RemoteDeploymentId = Object.assign( | ||
const BeaconProxyFactory = await getBeaconProxyFactory(hre, getSigner(attachTo.runner)); | ||
const proxyDeployment: Required<ProxyDeployment> & DeployTransaction & RemoteDeploymentId = Object.assign( | ||
{ kind: opts.kind }, | ||
@@ -90,0 +91,0 @@ await deploy(hre, opts, BeaconProxyFactory, beaconAddress, data), |
@@ -7,3 +7,5 @@ import type { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
import { DeployBeaconOptions, deploy, DeployTransaction, getUpgradeableBeaconFactory, deployBeaconImpl } from './utils'; | ||
import { disablePlatform } from './platform/utils'; | ||
import { disableDefender } from './defender/utils'; | ||
import { attach, getSigner } from './utils/ethers'; | ||
import { getInitialOwner } from './utils/initial-owner'; | ||
@@ -14,12 +16,22 @@ export interface DeployBeaconFunction { | ||
export function makeDeployBeacon(hre: HardhatRuntimeEnvironment, platformModule: boolean): DeployBeaconFunction { | ||
export function makeDeployBeacon(hre: HardhatRuntimeEnvironment, defenderModule: boolean): DeployBeaconFunction { | ||
return async function deployBeacon(ImplFactory: ContractFactory, opts: DeployBeaconOptions = {}) { | ||
disablePlatform(hre, platformModule, opts, deployBeacon.name); | ||
disableDefender(hre, defenderModule, opts, deployBeacon.name); | ||
const { impl } = await deployBeaconImpl(hre, ImplFactory, opts); | ||
const UpgradeableBeaconFactory = await getUpgradeableBeaconFactory(hre, ImplFactory.signer); | ||
const beaconDeployment: Deployment & DeployTransaction = await deploy(hre, opts, UpgradeableBeaconFactory, impl); | ||
const beaconContract = UpgradeableBeaconFactory.attach(beaconDeployment.address); | ||
const signer = getSigner(ImplFactory.runner); | ||
const UpgradeableBeaconFactory = await getUpgradeableBeaconFactory(hre, signer); | ||
const initialOwner = await getInitialOwner(opts, signer); | ||
const beaconDeployment: Deployment & DeployTransaction = await deploy( | ||
hre, | ||
opts, | ||
UpgradeableBeaconFactory, | ||
impl, | ||
initialOwner, | ||
); | ||
const beaconContract = attach(UpgradeableBeaconFactory, beaconDeployment.address); | ||
// @ts-ignore Won't be readonly because beaconContract was created through attach. | ||
@@ -26,0 +38,0 @@ beaconContract.deployTransaction = beaconDeployment.deployTransaction; |
@@ -6,3 +6,3 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
import { DeployData, getDeployData } from './utils/deploy-impl'; | ||
import { enablePlatform } from './platform/utils'; | ||
import { enableDefender } from './defender/utils'; | ||
import { | ||
@@ -34,3 +34,3 @@ Deployment, | ||
const deployment: Required<Deployment & DeployTransaction> & RemoteDeploymentId = await deploy( | ||
const deployment: Required<Deployment> & DeployTransaction & RemoteDeploymentId = await deploy( | ||
hre, | ||
@@ -58,3 +58,3 @@ opts, | ||
export function makeDeployContract(hre: HardhatRuntimeEnvironment, platformModule: boolean): DeployContractFunction { | ||
export function makeDeployContract(hre: HardhatRuntimeEnvironment, defenderModule: boolean): DeployContractFunction { | ||
return async function deployContract( | ||
@@ -70,6 +70,6 @@ Contract, | ||
opts = enablePlatform(hre, platformModule, opts); | ||
opts = enableDefender(hre, defenderModule, opts); | ||
if (!opts.usePlatformDeploy) { | ||
throw new Error(`The ${deployContract.name} function cannot have the \`usePlatformDeploy\` option disabled.`); | ||
if (!opts.useDefenderDeploy) { | ||
throw new Error(`The ${deployContract.name} function cannot have the \`useDefenderDeploy\` option disabled.`); | ||
} | ||
@@ -76,0 +76,0 @@ |
@@ -6,3 +6,3 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
import { deployUpgradeableImpl } from './utils/deploy-impl'; | ||
import { enablePlatform } from './platform/utils'; | ||
import { enableDefender } from './defender/utils'; | ||
@@ -14,14 +14,14 @@ export type DeployImplementationFunction = ( | ||
export type DeployImplementationResponse = string | ethers.providers.TransactionResponse; | ||
export type DeployImplementationResponse = string | ethers.TransactionResponse; | ||
export function makeDeployImplementation( | ||
hre: HardhatRuntimeEnvironment, | ||
platformModule: boolean, | ||
defenderModule: boolean, | ||
): DeployImplementationFunction { | ||
return async function deployImplementation(ImplFactory, opts: DeployImplementationOptions = {}) { | ||
opts = enablePlatform(hre, platformModule, opts); | ||
opts = enableDefender(hre, defenderModule, opts); | ||
const deployedImpl = await deployUpgradeableImpl(hre, ImplFactory, opts); | ||
if (opts.getTxResponse && deployedImpl.txResponse !== undefined) { | ||
if (opts.getTxResponse && deployedImpl.txResponse) { | ||
return deployedImpl.txResponse; | ||
@@ -28,0 +28,0 @@ } else { |
@@ -10,2 +10,3 @@ import type { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
RemoteDeploymentId, | ||
InitialOwnerUnsupportedKindError, | ||
} from '@openzeppelin/upgrades-core'; | ||
@@ -21,5 +22,7 @@ | ||
getInitializerData, | ||
getSigner, | ||
} from './utils'; | ||
import { enablePlatform } from './platform/utils'; | ||
import { enableDefender } from './defender/utils'; | ||
import { getContractInstance } from './utils/contract-instance'; | ||
import { getInitialOwner } from './utils/initial-owner'; | ||
@@ -31,3 +34,3 @@ export interface DeployFunction { | ||
export function makeDeployProxy(hre: HardhatRuntimeEnvironment, platformModule: boolean): DeployFunction { | ||
export function makeDeployProxy(hre: HardhatRuntimeEnvironment, defenderModule: boolean): DeployFunction { | ||
return async function deployProxy( | ||
@@ -43,3 +46,3 @@ ImplFactory: ContractFactory, | ||
opts = enablePlatform(hre, platformModule, opts); | ||
opts = enableDefender(hre, defenderModule, opts); | ||
@@ -54,4 +57,4 @@ const { provider } = hre.network; | ||
if (kind === 'uups') { | ||
if (await manifest.getAdmin()) { | ||
if (await manifest.getAdmin()) { | ||
if (kind === 'uups') { | ||
logWarning(`A proxy admin was previously deployed on this network`, [ | ||
@@ -61,6 +64,13 @@ `This is not natively used with the current kind of proxy ('uups').`, | ||
]); | ||
} else if (kind === 'transparent') { | ||
logWarning(`A proxy admin was previously deployed on this network`, [ | ||
`This is not used with new transparent proxy deployments, since new transparent proxies deploy their own admins.`, | ||
`Changes to the previous admin will have no effect on this new proxy.`, | ||
]); | ||
} | ||
} | ||
let proxyDeployment: Required<ProxyDeployment & DeployTransaction> & RemoteDeploymentId; | ||
const signer = getSigner(ImplFactory.runner); | ||
let proxyDeployment: Required<ProxyDeployment> & DeployTransaction & RemoteDeploymentId; | ||
switch (kind) { | ||
@@ -72,3 +82,7 @@ case 'beacon': { | ||
case 'uups': { | ||
const ProxyFactory = await getProxyFactory(hre, ImplFactory.signer); | ||
if (opts.initialOwner !== undefined) { | ||
throw new InitialOwnerUnsupportedKindError(kind); | ||
} | ||
const ProxyFactory = await getProxyFactory(hre, signer); | ||
proxyDeployment = Object.assign({ kind }, await deploy(hre, opts, ProxyFactory, impl, data)); | ||
@@ -79,7 +93,8 @@ break; | ||
case 'transparent': { | ||
const adminAddress = await hre.upgrades.deployProxyAdmin(ImplFactory.signer, opts); | ||
const TransparentUpgradeableProxyFactory = await getTransparentUpgradeableProxyFactory(hre, ImplFactory.signer); | ||
const initialOwner = await getInitialOwner(opts, signer); | ||
const TransparentUpgradeableProxyFactory = await getTransparentUpgradeableProxyFactory(hre, signer); | ||
proxyDeployment = Object.assign( | ||
{ kind }, | ||
await deploy(hre, opts, TransparentUpgradeableProxyFactory, impl, adminAddress, data), | ||
await deploy(hre, opts, TransparentUpgradeableProxyFactory, impl, initialOwner, data), | ||
); | ||
@@ -86,0 +101,0 @@ break; |
@@ -7,3 +7,2 @@ import type { EthereumProvider, HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
getImplementationAddressFromProxy, | ||
getAdminAddress, | ||
addProxyToManifest, | ||
@@ -17,2 +16,5 @@ isBeacon, | ||
NoContractImportError, | ||
getAdminAddress, | ||
isEmptySlot, | ||
UpgradesError, | ||
} from '@openzeppelin/upgrades-core'; | ||
@@ -27,4 +29,4 @@ | ||
} from './utils'; | ||
import { simulateDeployAdmin } from './utils/simulate-deploy'; | ||
import { getDeployData } from './utils/deploy-impl'; | ||
import { attach, getSigner } from './utils/ethers'; | ||
@@ -44,3 +46,3 @@ export interface ForceImportFunction { | ||
const address = getContractAddress(addressOrInstance); | ||
const address = await getContractAddress(addressOrInstance); | ||
@@ -51,3 +53,3 @@ const implAddress = await getImplementationAddressFromProxy(provider, address); | ||
return ImplFactory.attach(address); | ||
return attach(ImplFactory, address); | ||
} else if (await isBeacon(provider, address)) { | ||
@@ -57,4 +59,4 @@ const beaconImplAddress = await getImplementationAddressFromBeacon(provider, address); | ||
const UpgradeableBeaconFactory = await getUpgradeableBeaconFactory(hre, ImplFactory.signer); | ||
return UpgradeableBeaconFactory.attach(address); | ||
const UpgradeableBeaconFactory = await getUpgradeableBeaconFactory(hre, getSigner(ImplFactory.runner)); | ||
return attach(UpgradeableBeaconFactory, address); | ||
} else { | ||
@@ -65,3 +67,3 @@ if (!(await hasCode(provider, address))) { | ||
await addImplToManifest(hre, address, ImplFactory, opts); | ||
return ImplFactory.attach(address); | ||
return attach(ImplFactory, address); | ||
} | ||
@@ -95,4 +97,5 @@ }; | ||
if (importKind === 'transparent') { | ||
await addAdminToManifest(provider, hre, proxyAddress, ImplFactory, opts); | ||
await assertNonEmptyAdminSlot(provider, proxyAddress); | ||
} | ||
await addProxyToManifest(importKind, proxyAddress, manifest); | ||
@@ -110,11 +113,14 @@ } | ||
async function addAdminToManifest( | ||
provider: EthereumProvider, | ||
hre: HardhatRuntimeEnvironment, | ||
proxyAddress: string, | ||
ImplFactory: ContractFactory, | ||
opts: ForceImportOptions, | ||
) { | ||
async function assertNonEmptyAdminSlot(provider: EthereumProvider, proxyAddress: string) { | ||
const adminAddress = await getAdminAddress(provider, proxyAddress); | ||
await simulateDeployAdmin(hre, ImplFactory, opts, adminAddress); | ||
if (isEmptySlot(adminAddress)) { | ||
// Assert that the admin slot of a transparent proxy is not zero, otherwise the wrong kind may be imported. | ||
// Note: Transparent proxies should not have the zero address as the admin, according to TransparentUpgradeableProxy's _setAdmin function. | ||
throw new UpgradesError( | ||
`Proxy at ${proxyAddress} doesn't look like a transparent proxy`, | ||
() => | ||
`The proxy doesn't look like a transparent proxy because its admin address slot is empty. ` + | ||
`Set the \`kind\` option to the kind of proxy that was deployed at ${proxyAddress} (either 'uups' or 'beacon')`, | ||
); | ||
} | ||
} |
187
src/index.ts
/* eslint-disable @typescript-eslint/no-var-requires */ | ||
import '@nomiclabs/hardhat-ethers'; | ||
import '@nomicfoundation/hardhat-ethers'; | ||
import './type-extensions'; | ||
@@ -9,3 +9,3 @@ import { subtask, extendEnvironment, extendConfig } from 'hardhat/config'; | ||
import { HardhatConfig, HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
import { getImplementationAddressFromBeacon, silenceWarnings, SolcInput } from '@openzeppelin/upgrades-core'; | ||
import type { silenceWarnings, SolcInput, SolcOutput } from '@openzeppelin/upgrades-core'; | ||
import type { DeployFunction } from './deploy-proxy'; | ||
@@ -18,10 +18,20 @@ import type { PrepareUpgradeFunction } from './prepare-upgrade'; | ||
import type { ForceImportFunction } from './force-import'; | ||
import type { ChangeAdminFunction, TransferProxyAdminOwnershipFunction, GetInstanceFunction } from './admin'; | ||
import type { ChangeAdminFunction, TransferProxyAdminOwnershipFunction } from './admin'; | ||
import type { ValidateImplementationFunction } from './validate-implementation'; | ||
import type { ValidateUpgradeFunction } from './validate-upgrade'; | ||
import type { DeployImplementationFunction } from './deploy-implementation'; | ||
import { DeployAdminFunction, makeDeployProxyAdmin } from './deploy-proxy-admin'; | ||
import type { DeployContractFunction } from './deploy-contract'; | ||
import type { ProposeUpgradeFunction } from './platform/propose-upgrade'; | ||
import type { GetDefaultApprovalProcessFunction } from './platform/get-default-approval-process'; | ||
import type { ProposeUpgradeWithApprovalFunction } from './defender/propose-upgrade-with-approval'; | ||
import type { | ||
GetDeployApprovalProcessFunction, | ||
GetUpgradeApprovalProcessFunction, | ||
} from './defender/get-approval-process'; | ||
import type { ProposeUpgradeFunction } from './defender-v1/propose-upgrade'; | ||
import type { | ||
VerifyDeployFunction, | ||
VerifyDeployWithUploadedArtifactFunction, | ||
GetVerifyDeployArtifactFunction, | ||
GetVerifyDeployBuildInfoFunction, | ||
GetBytecodeDigestFunction, | ||
} from './defender-v1/verify-deployment'; | ||
@@ -38,7 +48,5 @@ export interface HardhatUpgrades { | ||
upgradeBeacon: UpgradeBeaconFunction; | ||
deployProxyAdmin: DeployAdminFunction; | ||
forceImport: ForceImportFunction; | ||
silenceWarnings: typeof silenceWarnings; | ||
admin: { | ||
getInstance: GetInstanceFunction; | ||
changeProxyAdmin: ChangeAdminFunction; | ||
@@ -57,11 +65,26 @@ transferProxyAdminOwnership: TransferProxyAdminOwnershipFunction; | ||
export interface PlatformHardhatUpgrades extends HardhatUpgrades { | ||
deployContract: DeployContractFunction; | ||
export interface DefenderV1HardhatUpgrades { | ||
proposeUpgrade: ProposeUpgradeFunction; | ||
getDefaultApprovalProcess: GetDefaultApprovalProcessFunction; | ||
verifyDeployment: VerifyDeployFunction; | ||
verifyDeploymentWithUploadedArtifact: VerifyDeployWithUploadedArtifactFunction; | ||
getDeploymentArtifact: GetVerifyDeployArtifactFunction; | ||
getDeploymentBuildInfo: GetVerifyDeployBuildInfoFunction; | ||
getBytecodeDigest: GetBytecodeDigestFunction; | ||
} | ||
export interface DefenderHardhatUpgrades extends HardhatUpgrades, DefenderV1HardhatUpgrades { | ||
deployContract: DeployContractFunction; | ||
proposeUpgradeWithApproval: ProposeUpgradeWithApprovalFunction; | ||
getDeployApprovalProcess: GetDeployApprovalProcessFunction; | ||
getUpgradeApprovalProcess: GetUpgradeApprovalProcessFunction; | ||
/** | ||
* @deprecated Use `getUpgradeApprovalProcess` instead. | ||
*/ | ||
getDefaultApprovalProcess: GetUpgradeApprovalProcessFunction; | ||
} | ||
interface RunCompilerArgs { | ||
input: SolcInput; | ||
solcVersion: string; | ||
quiet: boolean; | ||
} | ||
@@ -86,3 +109,5 @@ | ||
subtask(TASK_COMPILE_SOLIDITY_COMPILE, async (args: RunCompilerArgs, hre, runSuper) => { | ||
const { validate, solcInputOutputDecoder } = await import('@openzeppelin/upgrades-core'); | ||
const { isNamespaceSupported, validate, solcInputOutputDecoder, makeNamespacedInput } = await import( | ||
'@openzeppelin/upgrades-core' | ||
); | ||
const { writeValidations } = await import('./utils/validations'); | ||
@@ -96,3 +121,11 @@ | ||
const decodeSrc = solcInputOutputDecoder(args.input, output); | ||
const validations = validate(output, decodeSrc, args.solcVersion); | ||
let namespacedOutput = undefined; | ||
if (isNamespaceSupported(args.solcVersion)) { | ||
const namespacedInput = makeNamespacedInput(args.input, output); | ||
namespacedOutput = (await runSuper({ ...args, quiet: true, input: namespacedInput })).output; | ||
await checkNamespacedCompileErrors(namespacedOutput); | ||
} | ||
const validations = validate(output, decodeSrc, args.solcVersion, args.input, namespacedOutput); | ||
await writeValidations(hre, validations); | ||
@@ -104,2 +137,25 @@ } | ||
/** | ||
* Checks for compile errors in the modified contracts for namespaced storage. | ||
* If errors are found, throws an error with the compile error messages. | ||
*/ | ||
async function checkNamespacedCompileErrors(namespacedOutput: SolcOutput) { | ||
const errors = []; | ||
if (namespacedOutput.errors !== undefined) { | ||
for (const error of namespacedOutput.errors) { | ||
if (error.severity === 'error') { | ||
errors.push(error.formattedMessage); | ||
} | ||
} | ||
} | ||
if (errors.length > 0) { | ||
const { UpgradesError } = await import('@openzeppelin/upgrades-core'); | ||
throw new UpgradesError( | ||
`Failed to compile modified contracts for namespaced storage:\n\n${errors.join('\n')}`, | ||
() => | ||
'Please report this at https://zpl.in/upgrades/report. If possible, include the source code for the contracts mentioned in the errors above.', | ||
); | ||
} | ||
} | ||
extendEnvironment(hre => { | ||
@@ -110,16 +166,39 @@ hre.upgrades = lazyObject((): HardhatUpgrades => { | ||
hre.platform = lazyObject((): PlatformHardhatUpgrades => { | ||
return makePlatformFunctions(hre); | ||
warnOnHardhatDefender(); | ||
hre.defender = lazyObject((): DefenderHardhatUpgrades => { | ||
return makeDefenderFunctions(hre); | ||
}); | ||
}); | ||
function warnOnHardhatDefender() { | ||
if (tryRequire('@openzeppelin/hardhat-defender', true)) { | ||
const { logWarning } = require('@openzeppelin/upgrades-core'); | ||
logWarning('The @openzeppelin/hardhat-defender package is deprecated.', [ | ||
'Uninstall the @openzeppelin/hardhat-defender package.', | ||
'OpenZeppelin Defender integration is included as part of the Hardhat Upgrades plugin.', | ||
]); | ||
} | ||
} | ||
extendConfig((config: HardhatConfig) => { | ||
// Accumulate references to all the compiler settings, including overrides | ||
const settings = []; | ||
for (const compiler of config.solidity.compilers) { | ||
compiler.settings ??= {}; | ||
compiler.settings.outputSelection ??= {}; | ||
compiler.settings.outputSelection['*'] ??= {}; | ||
compiler.settings.outputSelection['*']['*'] ??= []; | ||
settings.push(compiler.settings); | ||
} | ||
for (const compilerOverride of Object.values(config.solidity.overrides)) { | ||
compilerOverride.settings ??= {}; | ||
settings.push(compilerOverride.settings); | ||
} | ||
if (!compiler.settings.outputSelection['*']['*'].includes('storageLayout')) { | ||
compiler.settings.outputSelection['*']['*'].push('storageLayout'); | ||
// Enable storage layout in all of them | ||
for (const setting of settings) { | ||
setting.outputSelection ??= {}; | ||
setting.outputSelection['*'] ??= {}; | ||
setting.outputSelection['*']['*'] ??= []; | ||
if (!setting.outputSelection['*']['*'].includes('storageLayout')) { | ||
setting.outputSelection['*']['*'].push('storageLayout'); | ||
} | ||
@@ -129,4 +208,4 @@ } | ||
if (tryRequire('@nomiclabs/hardhat-etherscan')) { | ||
subtask('verify:verify').setAction(async (args, hre, runSuper) => { | ||
if (tryRequire('@nomicfoundation/hardhat-verify')) { | ||
subtask('verify:etherscan').setAction(async (args, hre, runSuper) => { | ||
const { verify } = await import('./verify-proxy'); | ||
@@ -137,3 +216,3 @@ return await verify(args, hre, runSuper); | ||
function makeFunctions(hre: HardhatRuntimeEnvironment, platform: boolean) { | ||
function makeFunctions(hre: HardhatRuntimeEnvironment, defender: boolean) { | ||
const { | ||
@@ -144,2 +223,3 @@ silenceWarnings, | ||
getBeaconAddress, | ||
getImplementationAddressFromBeacon, | ||
} = require('@openzeppelin/upgrades-core'); | ||
@@ -156,21 +236,19 @@ const { makeDeployProxy } = require('./deploy-proxy'); | ||
const { makeForceImport } = require('./force-import'); | ||
const { makeChangeProxyAdmin, makeTransferProxyAdminOwnership, makeGetInstanceFunction } = require('./admin'); | ||
const { makeChangeProxyAdmin, makeTransferProxyAdminOwnership } = require('./admin'); | ||
return { | ||
silenceWarnings, | ||
deployProxy: makeDeployProxy(hre, platform), | ||
upgradeProxy: makeUpgradeProxy(hre, platform), // block on platform | ||
deployProxy: makeDeployProxy(hre, defender), | ||
upgradeProxy: makeUpgradeProxy(hre, defender), // block on defender | ||
validateImplementation: makeValidateImplementation(hre), | ||
validateUpgrade: makeValidateUpgrade(hre), | ||
deployImplementation: makeDeployImplementation(hre, platform), | ||
prepareUpgrade: makePrepareUpgrade(hre, platform), | ||
deployBeacon: makeDeployBeacon(hre, platform), // block on platform | ||
deployBeaconProxy: makeDeployBeaconProxy(hre, platform), | ||
upgradeBeacon: makeUpgradeBeacon(hre, platform), // block on platform | ||
deployProxyAdmin: makeDeployProxyAdmin(hre, platform), // block on platform | ||
deployImplementation: makeDeployImplementation(hre, defender), | ||
prepareUpgrade: makePrepareUpgrade(hre, defender), | ||
deployBeacon: makeDeployBeacon(hre, defender), // block on defender | ||
deployBeaconProxy: makeDeployBeaconProxy(hre, defender), | ||
upgradeBeacon: makeUpgradeBeacon(hre, defender), // block on defender | ||
forceImport: makeForceImport(hre), | ||
admin: { | ||
getInstance: makeGetInstanceFunction(hre), | ||
changeProxyAdmin: makeChangeProxyAdmin(hre, platform), // block on platform | ||
transferProxyAdminOwnership: makeTransferProxyAdminOwnership(hre, platform), // block on platform | ||
changeProxyAdmin: makeChangeProxyAdmin(hre, defender), // block on defender | ||
transferProxyAdminOwnership: makeTransferProxyAdminOwnership(hre, defender), // block on defender | ||
}, | ||
@@ -193,18 +271,43 @@ erc1967: { | ||
function makePlatformFunctions(hre: HardhatRuntimeEnvironment): PlatformHardhatUpgrades { | ||
function makeDefenderV1Functions(hre: HardhatRuntimeEnvironment): DefenderV1HardhatUpgrades { | ||
const { | ||
makeVerifyDeploy, | ||
makeVerifyDeployWithUploadedArtifact, | ||
makeGetVerifyDeployBuildInfo, | ||
makeGetVerifyDeployArtifact, | ||
makeGetBytecodeDigest, | ||
} = require('./defender-v1/verify-deployment'); | ||
const { makeProposeUpgrade } = require('./defender-v1/propose-upgrade'); | ||
return { | ||
proposeUpgrade: makeProposeUpgrade(hre), | ||
verifyDeployment: makeVerifyDeploy(hre), | ||
verifyDeploymentWithUploadedArtifact: makeVerifyDeployWithUploadedArtifact(hre), | ||
getDeploymentArtifact: makeGetVerifyDeployArtifact(hre), | ||
getDeploymentBuildInfo: makeGetVerifyDeployBuildInfo(hre), | ||
getBytecodeDigest: makeGetBytecodeDigest(hre), | ||
}; | ||
} | ||
function makeDefenderFunctions(hre: HardhatRuntimeEnvironment): DefenderHardhatUpgrades { | ||
const { makeDeployContract } = require('./deploy-contract'); | ||
const { makeProposeUpgrade } = require('./platform/propose-upgrade'); | ||
const { makeGetDefaultApprovalProcess } = require('./platform/get-default-approval-process'); | ||
const { makeProposeUpgradeWithApproval } = require('./defender/propose-upgrade-with-approval'); | ||
const { makeGetDeployApprovalProcess, makeGetUpgradeApprovalProcess } = require('./defender/get-approval-process'); | ||
const getUpgradeApprovalProcess = makeGetUpgradeApprovalProcess(hre); | ||
return { | ||
...makeFunctions(hre, true), | ||
...makeDefenderV1Functions(hre), | ||
deployContract: makeDeployContract(hre, true), | ||
proposeUpgrade: makeProposeUpgrade(hre, true), | ||
getDefaultApprovalProcess: makeGetDefaultApprovalProcess(hre), | ||
proposeUpgradeWithApproval: makeProposeUpgradeWithApproval(hre, true), | ||
getDeployApprovalProcess: makeGetDeployApprovalProcess(hre), | ||
getUpgradeApprovalProcess: getUpgradeApprovalProcess, | ||
getDefaultApprovalProcess: getUpgradeApprovalProcess, // deprecated, is an alias for getUpgradeApprovalProcess | ||
}; | ||
} | ||
function tryRequire(id: string) { | ||
function tryRequire(id: string, resolveOnly?: boolean) { | ||
try { | ||
require(id); | ||
resolveOnly ? require.resolve(id) : require(id); | ||
return true; | ||
@@ -211,0 +314,0 @@ } catch (e: any) { |
@@ -19,3 +19,3 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
import { DeployImplementationResponse } from './deploy-implementation'; | ||
import { enablePlatform } from './platform/utils'; | ||
import { enableDefender } from './defender/utils'; | ||
import { deployUpgradeableImpl, DeployedImpl } from './utils/deploy-impl'; | ||
@@ -29,9 +29,9 @@ | ||
export function makePrepareUpgrade(hre: HardhatRuntimeEnvironment, platformModule: boolean): PrepareUpgradeFunction { | ||
export function makePrepareUpgrade(hre: HardhatRuntimeEnvironment, defenderModule: boolean): PrepareUpgradeFunction { | ||
return async function prepareUpgrade(referenceAddressOrContract, ImplFactory, opts: PrepareUpgradeOptions = {}) { | ||
opts = enablePlatform(hre, platformModule, opts); | ||
opts = enableDefender(hre, defenderModule, opts); | ||
const deployedImpl = await deployImplForUpgrade(hre, referenceAddressOrContract, ImplFactory, opts); | ||
if (opts.getTxResponse && deployedImpl.txResponse !== undefined) { | ||
if (opts.getTxResponse && deployedImpl.txResponse) { | ||
return deployedImpl.txResponse; | ||
@@ -50,3 +50,3 @@ } else { | ||
): Promise<DeployedImpl> { | ||
const referenceAddress = getContractAddress(referenceAddressOrContract); | ||
const referenceAddress = await getContractAddress(referenceAddressOrContract); | ||
const { provider } = hre.network; | ||
@@ -53,0 +53,0 @@ let deployedImpl; |
import 'hardhat/types/runtime'; | ||
import 'hardhat/types/config'; | ||
import type { HardhatUpgrades, PlatformHardhatUpgrades } from '.'; | ||
import type { HardhatUpgrades, DefenderHardhatUpgrades } from '.'; | ||
@@ -8,10 +9,10 @@ declare module 'hardhat/types/runtime' { | ||
upgrades: HardhatUpgrades; | ||
platform: PlatformHardhatUpgrades; | ||
defender: DefenderHardhatUpgrades; | ||
} | ||
} | ||
export interface HardhatPlatformConfig { | ||
export interface HardhatDefenderConfig { | ||
apiKey: string; | ||
apiSecret: string; | ||
usePlatformDeploy?: boolean; | ||
useDefenderDeploy?: boolean; | ||
} | ||
@@ -21,8 +22,8 @@ | ||
export interface HardhatUserConfig { | ||
platform?: HardhatPlatformConfig; | ||
defender?: HardhatDefenderConfig; | ||
} | ||
export interface HardhatConfig { | ||
platform?: HardhatPlatformConfig; | ||
defender?: HardhatDefenderConfig; | ||
} | ||
} |
@@ -10,4 +10,6 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
UpgradeBeaconOptions, | ||
attach, | ||
getSigner, | ||
} from './utils'; | ||
import { disablePlatform } from './platform/utils'; | ||
import { disableDefender } from './defender/utils'; | ||
@@ -20,13 +22,15 @@ export type UpgradeBeaconFunction = ( | ||
export function makeUpgradeBeacon(hre: HardhatRuntimeEnvironment, platformModule: boolean): UpgradeBeaconFunction { | ||
export function makeUpgradeBeacon(hre: HardhatRuntimeEnvironment, defenderModule: boolean): UpgradeBeaconFunction { | ||
return async function upgradeBeacon(beacon, ImplFactory, opts: UpgradeBeaconOptions = {}) { | ||
disablePlatform(hre, platformModule, opts, upgradeBeacon.name); | ||
disableDefender(hre, defenderModule, opts, upgradeBeacon.name); | ||
const beaconAddress = getContractAddress(beacon); | ||
const beaconAddress = await getContractAddress(beacon); | ||
const { impl: nextImpl } = await deployBeaconImpl(hre, ImplFactory, opts, beaconAddress); | ||
const UpgradeableBeaconFactory = await getUpgradeableBeaconFactory(hre, ImplFactory.signer); | ||
const beaconContract = UpgradeableBeaconFactory.attach(beaconAddress); | ||
const upgradeTx = await beaconContract.upgradeTo(nextImpl); | ||
const UpgradeableBeaconFactory = await getUpgradeableBeaconFactory(hre, getSigner(ImplFactory.runner)); | ||
const beaconContract = attach(UpgradeableBeaconFactory, beaconAddress); | ||
const overrides = opts.txOverrides ? [opts.txOverrides] : []; | ||
const upgradeTx = await beaconContract.upgradeTo(nextImpl, ...overrides); | ||
// @ts-ignore Won't be readonly because beaconContract was created through attach. | ||
@@ -33,0 +37,0 @@ beaconContract.deployTransaction = upgradeTx; |
import { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
import type { ethers, ContractFactory, Contract, Signer } from 'ethers'; | ||
import debug from './utils/debug'; | ||
import { getAdminAddress, getCode, getUpgradeInterfaceVersion, isEmptySlot } from '@openzeppelin/upgrades-core'; | ||
import { Manifest, getAdminAddress, getCode, isEmptySlot } from '@openzeppelin/upgrades-core'; | ||
import { | ||
UpgradeProxyOptions, | ||
deployProxyImpl, | ||
getITransparentUpgradeableProxyFactory, | ||
getProxyAdminFactory, | ||
getContractAddress, | ||
ContractAddressOrInstance, | ||
getSigner, | ||
} from './utils'; | ||
import { disablePlatform } from './platform/utils'; | ||
import { disableDefender } from './defender/utils'; | ||
import { attach } from './utils/ethers'; | ||
import { | ||
attachITransparentUpgradeableProxyV4, | ||
attachITransparentUpgradeableProxyV5, | ||
attachProxyAdminV4, | ||
attachProxyAdminV5, | ||
} from './utils/attach-abi'; | ||
@@ -22,15 +28,19 @@ export type UpgradeFunction = ( | ||
export function makeUpgradeProxy(hre: HardhatRuntimeEnvironment, platformModule: boolean): UpgradeFunction { | ||
export function makeUpgradeProxy( | ||
hre: HardhatRuntimeEnvironment, | ||
defenderModule: boolean, | ||
log = debug, | ||
): UpgradeFunction { | ||
return async function upgradeProxy(proxy, ImplFactory, opts: UpgradeProxyOptions = {}) { | ||
disablePlatform(hre, platformModule, opts, upgradeProxy.name); | ||
disableDefender(hre, defenderModule, opts, upgradeProxy.name); | ||
const proxyAddress = getContractAddress(proxy); | ||
const proxyAddress = await getContractAddress(proxy); | ||
const { impl: nextImpl } = await deployProxyImpl(hre, ImplFactory, opts, proxyAddress); | ||
// upgrade kind is inferred above | ||
const upgradeTo = await getUpgrader(proxyAddress, ImplFactory.signer); | ||
const upgradeTo = await getUpgrader(proxyAddress, opts, getSigner(ImplFactory.runner)); | ||
const call = encodeCall(ImplFactory, opts.call); | ||
const upgradeTx = await upgradeTo(nextImpl, call); | ||
const inst = ImplFactory.attach(proxyAddress); | ||
const inst = attach(ImplFactory, proxyAddress); | ||
// @ts-ignore Won't be readonly because inst was created through attach. | ||
@@ -41,5 +51,5 @@ inst.deployTransaction = upgradeTx; | ||
type Upgrader = (nextImpl: string, call?: string) => Promise<ethers.providers.TransactionResponse>; | ||
type Upgrader = (nextImpl: string, call?: string) => Promise<ethers.TransactionResponse>; | ||
async function getUpgrader(proxyAddress: string, signer: Signer): Promise<Upgrader> { | ||
async function getUpgrader(proxyAddress: string, opts: UpgradeProxyOptions, signer?: Signer): Promise<Upgrader> { | ||
const { provider } = hre.network; | ||
@@ -50,21 +60,48 @@ | ||
const overrides = opts.txOverrides ? [opts.txOverrides] : []; | ||
if (isEmptySlot(adminAddress) || adminBytecode === '0x') { | ||
// No admin contract: use ITransparentUpgradeableProxyFactory to get proxiable interface | ||
const ITransparentUpgradeableProxyFactory = await getITransparentUpgradeableProxyFactory(hre, signer); | ||
const proxy = ITransparentUpgradeableProxyFactory.attach(proxyAddress); | ||
return (nextImpl, call) => (call ? proxy.upgradeToAndCall(nextImpl, call) : proxy.upgradeTo(nextImpl)); | ||
// No admin contract: use ITransparentUpgradeableProxy to get proxiable interface | ||
const upgradeInterfaceVersion = await getUpgradeInterfaceVersion(provider, proxyAddress, log); | ||
switch (upgradeInterfaceVersion) { | ||
case '5.0.0': { | ||
const proxy = await attachITransparentUpgradeableProxyV5(hre, proxyAddress, signer); | ||
return (nextImpl, call) => proxy.upgradeToAndCall(nextImpl, call ?? '0x', ...overrides); | ||
} | ||
default: { | ||
if (upgradeInterfaceVersion !== undefined) { | ||
// Log as debug if the interface version is an unknown string. | ||
// Do not throw an error because this could be caused by a fallback function. | ||
log( | ||
`Unknown UPGRADE_INTERFACE_VERSION ${upgradeInterfaceVersion} for proxy at ${proxyAddress}. Expected 5.0.0`, | ||
); | ||
} | ||
const proxy = await attachITransparentUpgradeableProxyV4(hre, proxyAddress, signer); | ||
return (nextImpl, call) => | ||
call ? proxy.upgradeToAndCall(nextImpl, call, ...overrides) : proxy.upgradeTo(nextImpl, ...overrides); | ||
} | ||
} | ||
} else { | ||
// Admin contract: redirect upgrade call through it | ||
const manifest = await Manifest.forNetwork(provider); | ||
const AdminFactory = await getProxyAdminFactory(hre, signer); | ||
const admin = AdminFactory.attach(adminAddress); | ||
const manifestAdmin = await manifest.getAdmin(); | ||
if (admin.address !== manifestAdmin?.address) { | ||
throw new Error('Proxy admin is not the one registered in the network manifest'); | ||
const upgradeInterfaceVersion = await getUpgradeInterfaceVersion(provider, adminAddress, log); | ||
switch (upgradeInterfaceVersion) { | ||
case '5.0.0': { | ||
const admin = await attachProxyAdminV5(hre, adminAddress, signer); | ||
return (nextImpl, call) => admin.upgradeAndCall(proxyAddress, nextImpl, call ?? '0x', ...overrides); | ||
} | ||
default: { | ||
if (upgradeInterfaceVersion !== undefined) { | ||
// Log as debug if the interface version is an unknown string. | ||
// Do not throw an error because this could be caused by a fallback function. | ||
log( | ||
`Unknown UPGRADE_INTERFACE_VERSION ${upgradeInterfaceVersion} for proxy admin at ${adminAddress}. Expected 5.0.0`, | ||
); | ||
} | ||
const admin = await attachProxyAdminV4(hre, adminAddress, signer); | ||
return (nextImpl, call) => | ||
call | ||
? admin.upgradeAndCall(proxyAddress, nextImpl, call, ...overrides) | ||
: admin.upgrade(proxyAddress, nextImpl, ...overrides); | ||
} | ||
} | ||
return (nextImpl, call) => | ||
call ? admin.upgradeAndCall(proxyAddress, nextImpl, call) : admin.upgrade(proxyAddress, nextImpl); | ||
} | ||
@@ -71,0 +108,0 @@ } |
@@ -5,14 +5,15 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
import { DeployTransaction, Platform } from '.'; | ||
import { waitForDeployment } from '../platform/utils'; | ||
import { DeployTransaction, DefenderDeploy } from '.'; | ||
import { waitForDeployment } from '../defender/utils'; | ||
import { Deployment, RemoteDeploymentId, DeployOpts } from '@openzeppelin/upgrades-core'; | ||
import { attach } from './ethers'; | ||
/** | ||
* Gets a contract instance from a deployment, where the deployment may be remote. | ||
* If the deployment is remote, the instance have an overriden `deployed` method to wait for the remote deployment | ||
* and update its `deployTransaction` with the new transaction hash if it was detected to have changed. | ||
* If the deployment is remote, the instance has an overriden `waitForDeployment` method to wait for the remote deployment | ||
* and update its `deploymentTransaction` with the new transaction hash if it was detected to have changed. | ||
* | ||
* @param hre The Hardhat Runtime Environment | ||
* @param contract The contract factory | ||
* @param opts The deploy and platform options | ||
* @param opts The deploy and defender options | ||
* @param deployment The deployment | ||
@@ -25,22 +26,28 @@ * @param deployTransaction The transaction that deployed the contract, if available | ||
contract: ContractFactory, | ||
opts: DeployOpts & Platform, | ||
opts: DeployOpts & DefenderDeploy, | ||
deployment: Deployment & DeployTransaction & RemoteDeploymentId, | ||
) { | ||
const instance = contract.attach(deployment.address); | ||
const instance = attach(contract, deployment.address); | ||
// @ts-ignore Won't be readonly because instance was created through attach. | ||
instance.deployTransaction = deployment.deployTransaction; | ||
instance.deploymentTransaction = () => deployment.deployTransaction ?? null; // Convert undefined to null to conform to ethers.js types. | ||
if (opts.usePlatformDeploy && deployment.remoteDeploymentId !== undefined) { | ||
const origDeployed = instance.deployed.bind(instance); | ||
instance.deployed = async () => { | ||
if (opts.useDefenderDeploy && deployment.remoteDeploymentId !== undefined) { | ||
const origWait = instance.waitForDeployment.bind(instance); | ||
instance.waitForDeployment = async () => { | ||
assert(deployment.remoteDeploymentId !== undefined); | ||
const updatedTxHash = await waitForDeployment(hre, opts, instance.address, deployment.remoteDeploymentId); | ||
const updatedTxHash = await waitForDeployment( | ||
hre, | ||
opts, | ||
await instance.getAddress(), | ||
deployment.remoteDeploymentId, | ||
); | ||
if (updatedTxHash !== undefined && updatedTxHash !== deployment.txHash) { | ||
const updatedTx = await hre.ethers.provider.getTransaction(updatedTxHash); | ||
// @ts-ignore Won't be readonly because instance was created through attach. | ||
instance.deployTransaction = await hre.ethers.provider.getTransaction(updatedTxHash); | ||
instance.deploymentTransaction = () => updatedTx; | ||
} | ||
return await origDeployed(); | ||
return await origWait(); | ||
}; | ||
@@ -47,0 +54,0 @@ } |
@@ -1,9 +0,9 @@ | ||
export type ContractAddressOrInstance = string | { address: string }; | ||
export type ContractAddressOrInstance = string | { getAddress(): Promise<string> }; | ||
export function getContractAddress(addressOrInstance: ContractAddressOrInstance): string { | ||
export async function getContractAddress(addressOrInstance: ContractAddressOrInstance): Promise<string> { | ||
if (typeof addressOrInstance === 'string') { | ||
return addressOrInstance; | ||
} else { | ||
return addressOrInstance.address; | ||
return await addressOrInstance.getAddress(); | ||
} | ||
} |
@@ -13,7 +13,6 @@ import { | ||
import type { ContractFactory, ethers } from 'ethers'; | ||
import { FormatTypes } from 'ethers/lib/utils'; | ||
import type { EthereumProvider, HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
import { deploy } from './deploy'; | ||
import { GetTxResponse, PlatformDeployOptions, StandaloneOptions, UpgradeOptions, withDefaults } from './options'; | ||
import { getRemoteDeployment } from '../platform/utils'; | ||
import { GetTxResponse, DefenderDeployOptions, StandaloneOptions, UpgradeOptions, withDefaults } from './options'; | ||
import { getRemoteDeployment } from '../defender/utils'; | ||
import { validateBeaconImpl, validateProxyImpl, validateImpl } from './validate-impl'; | ||
@@ -24,3 +23,3 @@ import { readValidations } from './validations'; | ||
impl: string; | ||
txResponse?: ethers.providers.TransactionResponse; | ||
txResponse?: ethers.TransactionResponse; | ||
} | ||
@@ -100,3 +99,3 @@ | ||
ImplFactory: ContractFactory, | ||
opts: UpgradeOptions & GetTxResponse & PlatformDeployOptions, | ||
opts: UpgradeOptions & GetTxResponse & DefenderDeployOptions, | ||
): Promise<DeployedImpl> { | ||
@@ -117,3 +116,3 @@ const layout = deployData.layout; | ||
async () => { | ||
const abi = ImplFactory.interface.format(FormatTypes.minimal) as string[]; | ||
const abi = ImplFactory.interface.format(true); | ||
const attemptDeploy = () => { | ||
@@ -143,5 +142,5 @@ if (deployData.fullOpts.useDeployedImplementation || deployData.fullOpts.redeployImplementation === 'never') { | ||
if ('deployTransaction' in deployment) { | ||
txResponse = deployment.deployTransaction; | ||
txResponse = deployment.deployTransaction ?? undefined; | ||
} else if (deployment.txHash !== undefined) { | ||
txResponse = await hre.ethers.provider.getTransaction(deployment.txHash); | ||
txResponse = (await hre.ethers.provider.getTransaction(deployment.txHash)) ?? undefined; | ||
} | ||
@@ -148,0 +147,0 @@ } |
import type { Deployment, RemoteDeploymentId } from '@openzeppelin/upgrades-core'; | ||
import debug from './debug'; | ||
import type { ethers, ContractFactory } from 'ethers'; | ||
import { getContractAddress } from 'ethers/lib/utils'; | ||
import type { ethers, ContractFactory, ContractMethodArgs } from 'ethers'; | ||
import { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
import { platformDeploy } from '../platform/deploy'; | ||
import { PlatformDeployOptions, UpgradeOptions } from './options'; | ||
import { defenderDeploy } from '../defender/deploy'; | ||
import { EthersDeployOptions, DefenderDeployOptions, UpgradeOptions } from './options'; | ||
export interface DeployTransaction { | ||
deployTransaction: ethers.providers.TransactionResponse; | ||
deployTransaction?: ethers.TransactionResponse; | ||
} | ||
@@ -15,9 +13,13 @@ | ||
hre: HardhatRuntimeEnvironment, | ||
opts: UpgradeOptions & PlatformDeployOptions, | ||
opts: UpgradeOptions & EthersDeployOptions & DefenderDeployOptions, | ||
factory: ContractFactory, | ||
...args: unknown[] | ||
): Promise<Required<Deployment & DeployTransaction> & RemoteDeploymentId> { | ||
if (opts?.usePlatformDeploy) { | ||
return await platformDeploy(hre, factory, opts, ...args); | ||
): Promise<Required<Deployment> & DeployTransaction & RemoteDeploymentId> { | ||
// defender always includes RemoteDeploymentId, while ethers always includes DeployTransaction | ||
if (opts?.useDefenderDeploy) { | ||
return await defenderDeploy(hre, factory, opts, ...args); | ||
} else { | ||
if (opts.txOverrides !== undefined) { | ||
args.push(opts.txOverrides); | ||
} | ||
return await ethersDeploy(factory, ...args); | ||
@@ -27,18 +29,17 @@ } | ||
async function ethersDeploy(factory: ContractFactory, ...args: unknown[]) { | ||
async function ethersDeploy( | ||
factory: ContractFactory, | ||
...args: ContractMethodArgs<unknown[]> | ||
): Promise<Required<Deployment & DeployTransaction> & RemoteDeploymentId> { | ||
const contractInstance = await factory.deploy(...args); | ||
const { deployTransaction } = contractInstance; | ||
const address: string = getContractAddress({ | ||
from: await factory.signer.getAddress(), | ||
nonce: deployTransaction.nonce, | ||
}); | ||
if (address !== contractInstance.address) { | ||
debug( | ||
`overriding contract address from ${contractInstance.address} to ${address} for nonce ${deployTransaction.nonce}`, | ||
); | ||
const deployTransaction = contractInstance.deploymentTransaction(); | ||
if (deployTransaction === null) { | ||
throw new Error('Broken invariant: deploymentTransaction is null'); | ||
} | ||
const address = await contractInstance.getAddress(); | ||
const txHash = deployTransaction.hash; | ||
return { address, txHash, deployTransaction }; | ||
} |
@@ -1,6 +0,3 @@ | ||
import { resolveEtherscanApiKey } from '@nomiclabs/hardhat-etherscan/dist/src/resolveEtherscanApiKey'; | ||
import { UpgradesError } from '@openzeppelin/upgrades-core'; | ||
import { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
import { EtherscanConfig, EtherscanNetworkEntry } from '@nomiclabs/hardhat-etherscan/dist/src/types'; | ||
@@ -10,2 +7,3 @@ import { request } from 'undici'; | ||
import debug from './debug'; | ||
import { Etherscan } from '@nomicfoundation/hardhat-verify/etherscan'; | ||
@@ -15,13 +13,11 @@ /** | ||
* | ||
* @param etherscanApi The Etherscan API config | ||
* @param etherscan Etherscan instance | ||
* @param params The API parameters to call with | ||
* @returns The Etherscan API response | ||
*/ | ||
export async function callEtherscanApi(etherscanApi: EtherscanAPIConfig, params: any): Promise<EtherscanResponseBody> { | ||
const parameters = new URLSearchParams({ ...params, apikey: etherscanApi.key }); | ||
const response = await request(etherscanApi.endpoints.urls.apiURL, { | ||
export async function callEtherscanApi(etherscan: Etherscan, params: any): Promise<EtherscanResponseBody> { | ||
const parameters = { ...params, apikey: etherscan.apiKey }; | ||
const response = await request(etherscan.apiUrl, { | ||
method: 'POST', | ||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, | ||
body: parameters.toString(), | ||
query: parameters, | ||
}); | ||
@@ -39,22 +35,26 @@ | ||
return responseBodyJson; | ||
return responseBodyJson as EtherscanResponseBody; | ||
} | ||
/** | ||
* Gets the Etherscan API parameters from Hardhat config. | ||
* Makes use of Hardhat Etherscan for handling cases when Etherscan API parameters are not present in config. | ||
* Gets an Etherscan instance based on Hardhat config. | ||
* Throws an error if Etherscan API key is not present in config. | ||
*/ | ||
export async function getEtherscanAPIConfig(hre: HardhatRuntimeEnvironment): Promise<EtherscanAPIConfig> { | ||
const endpoints = await hre.run('verify:get-etherscan-endpoint'); | ||
const etherscanConfig: EtherscanConfig = (hre.config as any).etherscan; | ||
const key = resolveEtherscanApiKey(etherscanConfig.apiKey, endpoints.network); | ||
return { key, endpoints }; | ||
export async function getEtherscanInstance(hre: HardhatRuntimeEnvironment): Promise<Etherscan> { | ||
const etherscanConfig: EtherscanConfig | undefined = (hre.config as any).etherscan; // This should never be undefined, but check just in case | ||
const chainConfig = await Etherscan.getCurrentChainConfig( | ||
hre.network.name, | ||
hre.network.provider, | ||
etherscanConfig?.customChains ?? [], | ||
); | ||
return Etherscan.fromChainConfig(etherscanConfig?.apiKey, chainConfig); | ||
} | ||
/** | ||
* The Etherscan API parameters from the Hardhat config. | ||
* Etherscan configuration for hardhat-verify. | ||
*/ | ||
export interface EtherscanAPIConfig { | ||
key: string; | ||
endpoints: EtherscanNetworkEntry; | ||
interface EtherscanConfig { | ||
apiKey: string | Record<string, string>; | ||
customChains: any[]; | ||
} | ||
@@ -68,5 +68,25 @@ | ||
message: string; | ||
result: any; | ||
result: unknown; | ||
} | ||
export const RESPONSE_OK = '1'; | ||
export async function verifyAndGetStatus( | ||
params: { | ||
contractAddress: string; | ||
sourceCode: string; | ||
contractName: string; | ||
compilerVersion: string; | ||
constructorArguments: string; | ||
}, | ||
etherscan: Etherscan, | ||
) { | ||
const response = await etherscan.verify( | ||
params.contractAddress, | ||
params.sourceCode, | ||
params.contractName, | ||
params.compilerVersion, | ||
params.constructorArguments, | ||
); | ||
return etherscan.getVerificationStatus(response.message); | ||
} |
@@ -0,9 +1,9 @@ | ||
import { ContractFactory, Signer } from 'ethers'; | ||
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 { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
import { Signer, ContractFactory } from 'ethers'; | ||
import ERC1967Proxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json'; | ||
import BeaconProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol/BeaconProxy.json'; | ||
import UpgradeableBeacon from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json'; | ||
import TransparentUpgradeableProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json'; | ||
import ITransparentUpgradeableProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/ITransparentUpgradeableProxy.json'; | ||
import ProxyAdmin from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.json'; | ||
@@ -21,13 +21,2 @@ export async function getProxyFactory(hre: HardhatRuntimeEnvironment, signer?: Signer): Promise<ContractFactory> { | ||
export async function getITransparentUpgradeableProxyFactory( | ||
hre: HardhatRuntimeEnvironment, | ||
signer?: Signer, | ||
): Promise<ContractFactory> { | ||
return hre.ethers.getContractFactory(ITransparentUpgradeableProxy.abi, ITransparentUpgradeableProxy.bytecode, signer); | ||
} | ||
export async function getProxyAdminFactory(hre: HardhatRuntimeEnvironment, signer?: Signer): Promise<ContractFactory> { | ||
return hre.ethers.getContractFactory(ProxyAdmin.abi, ProxyAdmin.bytecode, signer); | ||
} | ||
export async function getBeaconProxyFactory(hre: HardhatRuntimeEnvironment, signer?: Signer): Promise<ContractFactory> { | ||
@@ -34,0 +23,0 @@ return hre.ethers.getContractFactory(BeaconProxy.abi, BeaconProxy.bytecode, signer); |
@@ -10,1 +10,8 @@ export * from './deploy'; | ||
export * from './initializer-data'; | ||
export { attach, getSigner } from './ethers'; | ||
export { | ||
attachITransparentUpgradeableProxyV4, | ||
attachITransparentUpgradeableProxyV5, | ||
attachProxyAdminV4, | ||
attachProxyAdminV5, | ||
} from './attach-abi'; |
@@ -1,2 +0,3 @@ | ||
import { Interface } from '@ethersproject/abi'; | ||
import { UpgradesError } from '@openzeppelin/upgrades-core'; | ||
import { Interface } from 'ethers'; | ||
@@ -15,13 +16,16 @@ export function getInitializerData( | ||
try { | ||
const fragment = contractInterface.getFunction(initializer); | ||
const fragment = contractInterface.getFunction(initializer); | ||
if (fragment === null) { | ||
if (allowNoInitialization) { | ||
return '0x'; | ||
} else { | ||
throw new UpgradesError( | ||
`The contract has no initializer function matching the name or signature: ${initializer}`, | ||
() => | ||
`Ensure that the initializer function exists, specify an existing function with the 'initializer' option, or set the 'initializer' option to false to omit the initializer call.`, | ||
); | ||
} | ||
} else { | ||
return contractInterface.encodeFunctionData(fragment, args); | ||
} catch (e: unknown) { | ||
if (e instanceof Error) { | ||
if (allowNoInitialization && e.message.includes('no matching function')) { | ||
return '0x'; | ||
} | ||
} | ||
throw e; | ||
} | ||
} |
@@ -8,2 +8,3 @@ import { | ||
} from '@openzeppelin/upgrades-core'; | ||
import { Overrides } from 'ethers'; | ||
@@ -14,3 +15,4 @@ /** | ||
export type StandaloneOptions = StandaloneValidationOptions & | ||
DeployOpts & { | ||
DeployOpts & | ||
EthersDeployOptions & { | ||
constructorArgs?: unknown[]; | ||
@@ -36,2 +38,3 @@ /** | ||
redeployImplementation: opts.redeployImplementation ?? 'onchange', | ||
txOverrides: opts.txOverrides ?? {}, | ||
...withValidationDefaults(opts), | ||
@@ -53,34 +56,52 @@ }; | ||
/** | ||
* Option to enable or disable Platform deployments. | ||
* Option to enable or disable Defender deployments. | ||
*/ | ||
export type Platform = { | ||
usePlatformDeploy?: boolean; | ||
export type DefenderDeploy = { | ||
useDefenderDeploy?: boolean; | ||
}; | ||
/** | ||
* Options for functions that support Platform deployments. | ||
* Options for functions that support Defender deployments. | ||
*/ | ||
export type PlatformDeployOptions = Platform & { | ||
export type DefenderDeployOptions = DefenderDeploy & { | ||
verifySourceCode?: boolean; | ||
relayerId?: string; | ||
salt?: string; | ||
createFactoryAddress?: string; | ||
}; | ||
export type DeployBeaconProxyOptions = DeployOpts & ProxyKindOption & Initializer & PlatformDeployOptions; | ||
export type DeployBeaconOptions = StandaloneOptions & Platform; | ||
export type DeployImplementationOptions = StandaloneOptions & GetTxResponse & PlatformDeployOptions; | ||
export type DeployContractOptions = StandaloneOptions & | ||
/** | ||
* Options for functions that support deployments through ethers.js. | ||
*/ | ||
export type EthersDeployOptions = { | ||
/** | ||
* Overrides for the transaction sent to deploy a contract. | ||
*/ | ||
txOverrides?: Overrides; | ||
}; | ||
export type InitialOwner = { | ||
initialOwner?: string; | ||
}; | ||
export type DeployBeaconProxyOptions = EthersDeployOptions & | ||
DeployOpts & | ||
ProxyKindOption & | ||
Initializer & | ||
DefenderDeployOptions; | ||
export type DeployBeaconOptions = StandaloneOptions & InitialOwner & DefenderDeploy; | ||
export type DeployImplementationOptions = StandaloneOptions & GetTxResponse & DefenderDeployOptions; | ||
export type DeployContractOptions = Omit<StandaloneOptions, 'txOverrides'> & // ethers deployment not supported for deployContract | ||
GetTxResponse & | ||
PlatformDeployOptions & { | ||
DefenderDeployOptions & { | ||
unsafeAllowDeployContract?: boolean; | ||
}; | ||
export type DeployProxyAdminOptions = DeployOpts & Platform; | ||
export type DeployProxyOptions = StandaloneOptions & Initializer & PlatformDeployOptions; | ||
export type DeployProxyOptions = StandaloneOptions & Initializer & InitialOwner & DefenderDeployOptions; | ||
export type ForceImportOptions = ProxyKindOption; | ||
export type PrepareUpgradeOptions = UpgradeOptions & GetTxResponse & PlatformDeployOptions; | ||
export type UpgradeBeaconOptions = UpgradeOptions & Platform; | ||
export type PrepareUpgradeOptions = UpgradeOptions & GetTxResponse & DefenderDeployOptions; | ||
export type UpgradeBeaconOptions = UpgradeOptions & DefenderDeploy; | ||
export type UpgradeProxyOptions = UpgradeOptions & { | ||
call?: { fn: string; args?: unknown[] } | string; | ||
} & Platform; | ||
} & DefenderDeploy; | ||
export type ValidateImplementationOptions = StandaloneValidationOptions; | ||
export type ValidateUpgradeOptions = ValidationOptions; |
@@ -1,4 +0,3 @@ | ||
import { fetchOrDeploy, fetchOrDeployAdmin, logWarning } from '@openzeppelin/upgrades-core'; | ||
import { fetchOrDeploy } from '@openzeppelin/upgrades-core'; | ||
import type { ContractFactory } from 'ethers'; | ||
import { FormatTypes } from 'ethers/lib/utils'; | ||
import type { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
@@ -13,21 +12,2 @@ import { getDeployData } from './deploy-impl'; | ||
export async function simulateDeployAdmin( | ||
hre: HardhatRuntimeEnvironment, | ||
ProxyAdminFactory: ContractFactory, | ||
opts: UpgradeOptions, | ||
adminAddress: string, | ||
) { | ||
const { deployData, simulateDeploy } = await getSimulatedData(hre, ProxyAdminFactory, opts, adminAddress); | ||
const manifestAdminAddress = await fetchOrDeployAdmin(deployData.provider, simulateDeploy, opts); | ||
if (adminAddress !== manifestAdminAddress) { | ||
logWarning( | ||
`Imported proxy with admin at '${adminAddress}' which differs from previously deployed admin '${manifestAdminAddress}'`, | ||
[ | ||
`The imported proxy admin is different from the proxy admin that was previously deployed on this network. This proxy will not be upgradable directly by the plugin.`, | ||
`To upgrade this proxy, use the prepareUpgrade or defender.proposeUpgrade function and then upgrade it using the admin at '${adminAddress}'.`, | ||
], | ||
); | ||
} | ||
} | ||
export async function simulateDeployImpl( | ||
@@ -55,3 +35,3 @@ hre: HardhatRuntimeEnvironment, | ||
return { | ||
abi: ImplFactory.interface.format(FormatTypes.minimal) as string[], | ||
abi: ImplFactory.interface.format(true), | ||
layout: deployData.layout, | ||
@@ -58,0 +38,0 @@ address: implAddress, |
@@ -47,3 +47,3 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
} else { | ||
const referenceAddress = getContractAddress(referenceAddressOrImplFactory); | ||
const referenceAddress = await getContractAddress(referenceAddressOrImplFactory); | ||
const { provider } = hre.network; | ||
@@ -50,0 +50,0 @@ const deployData = await getDeployData(hre, newImplFactory, opts); |
import { | ||
toCheckStatusRequest, | ||
toVerifyRequest, | ||
} from '@nomiclabs/hardhat-etherscan/dist/src/etherscan/EtherscanVerifyContractRequest'; | ||
import { | ||
getVerificationStatus, | ||
verifyContract, | ||
} from '@nomiclabs/hardhat-etherscan/dist/src/etherscan/EtherscanService'; | ||
import { | ||
getTransactionByHash, | ||
@@ -21,12 +12,13 @@ getImplementationAddress, | ||
isEmptySlot, | ||
getCode, | ||
} from '@openzeppelin/upgrades-core'; | ||
import artifactsBuildInfo from '@openzeppelin/upgrades-core/artifacts/build-info.json'; | ||
import artifactsBuildInfo from '@openzeppelin/upgrades-core/artifacts/build-info-v5.json'; | ||
import { HardhatRuntimeEnvironment, RunSuperFunction } from 'hardhat/types'; | ||
import ERC1967Proxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json'; | ||
import BeaconProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol/BeaconProxy.json'; | ||
import UpgradeableBeacon from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json'; | ||
import TransparentUpgradeableProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json'; | ||
import ProxyAdmin from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol/ProxyAdmin.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'; | ||
@@ -36,3 +28,5 @@ import { keccak256 } from 'ethereumjs-util'; | ||
import debug from './utils/debug'; | ||
import { callEtherscanApi, EtherscanAPIConfig, getEtherscanAPIConfig, RESPONSE_OK } from './utils/etherscan-api'; | ||
import { callEtherscanApi, getEtherscanInstance, RESPONSE_OK } from './utils/etherscan-api'; | ||
import { verifyAndGetStatus } from './utils/etherscan-api'; | ||
import { Etherscan } from '@nomicfoundation/hardhat-verify/etherscan'; | ||
@@ -47,2 +41,3 @@ /** | ||
bytecode: any; | ||
deployedBytecode: any; | ||
} | ||
@@ -64,2 +59,10 @@ | ||
/** | ||
* Etherscan API response when getting event logs by address and topic. | ||
*/ | ||
interface EtherscanEventResponse { | ||
topics: string[]; | ||
transactionHash: string; | ||
} | ||
/** | ||
* The proxy-related contracts and their corresponding events that may have been deployed the current version of this plugin. | ||
@@ -76,10 +79,10 @@ */ | ||
/** | ||
* Overrides hardhat-etherscan's verify:verify subtask to fully verify a proxy or beacon. | ||
* Overrides hardhat-verify's verify:etherscan subtask 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-etherscan's verify function directly. | ||
* as well as the implementation. Otherwise, calls hardhat-verify's verify function directly. | ||
* | ||
* @param args Args to the hardhat-etherscan verify function | ||
* @param args Args to the hardhat-verify verify function | ||
* @param hre | ||
* @param runSuper The parent function which is expected to be hardhat-etherscan's verify function | ||
* @param runSuper The parent function which is expected to be hardhat-verify's verify function | ||
* @returns | ||
@@ -90,9 +93,9 @@ */ | ||
throw new UpgradesError( | ||
'The hardhat-etherscan plugin must be imported before the hardhat-upgrades plugin.', | ||
'The hardhat-verify plugin must be imported before the hardhat-upgrades plugin.', | ||
() => | ||
'Import the plugins in the following order in hardhat.config.js:\n' + | ||
' require("@nomiclabs/hardhat-etherscan");\n' + | ||
' require("@nomicfoundation/hardhat-verify");\n' + | ||
' require("@openzeppelin/hardhat-upgrades");\n' + | ||
'Or if you are using TypeScript, import the plugins in the following order in hardhat.config.ts:\n' + | ||
' import "@nomiclabs/hardhat-etherscan";\n' + | ||
' import "@nomicfoundation/hardhat-verify";\n' + | ||
' import "@openzeppelin/hardhat-upgrades";\n', | ||
@@ -117,4 +120,4 @@ ); | ||
proxy = false; | ||
const etherscanApi = await getEtherscanAPIConfig(hre); | ||
await fullVerifyBeacon(hre, proxyAddress, hardhatVerify, etherscanApi, errorReport); | ||
const etherscan = await getEtherscanInstance(hre); | ||
await fullVerifyBeacon(hre, proxyAddress, hardhatVerify, etherscan, errorReport); | ||
} else { | ||
@@ -176,6 +179,18 @@ // Doesn't look like a proxy, so just verify directly | ||
/** | ||
* Indicates that the expected event topic was not found in the contract's logs according to the Etherscan API. | ||
* Indicates that the expected event topic was not found in the contract's logs according to the Etherscan API, or an expected function was not found. | ||
*/ | ||
class EventNotFound extends UpgradesError {} | ||
class EventOrFunctionNotFound extends UpgradesError {} | ||
class EventsNotFound extends EventOrFunctionNotFound { | ||
constructor(address: string, events: string[]) { | ||
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.', | ||
); | ||
} | ||
} | ||
/** | ||
@@ -204,3 +219,3 @@ * Indicates that the contract's bytecode does not match with the plugin's artifact. | ||
* @param proxyAddress The transparent or UUPS proxy address | ||
* @param hardhatVerify A function that invokes the hardhat-etherscan plugin's verify command | ||
* @param hardhatVerify A function that invokes the hardhat-verify plugin's verify command | ||
* @param errorReport Accumulated verification errors | ||
@@ -218,6 +233,6 @@ */ | ||
const etherscanApi = await getEtherscanAPIConfig(hre); | ||
const etherscan = await getEtherscanInstance(hre); | ||
await verifyTransparentOrUUPS(); | ||
await linkProxyWithImplementationAbi(etherscanApi, proxyAddress, implAddress, errorReport); | ||
await linkProxyWithImplementationAbi(etherscan, proxyAddress, implAddress, errorReport); | ||
// Either UUPS or Transparent proxy could have admin slot set, although typically this should only be for Transparent | ||
@@ -230,22 +245,55 @@ await verifyAdmin(); | ||
console.log(`Verifying proxy admin: ${adminAddress}`); | ||
try { | ||
await verifyWithArtifactOrFallback( | ||
hre, | ||
hardhatVerify, | ||
etherscanApi, | ||
adminAddress, | ||
[verifiableContracts.proxyAdmin], | ||
errorReport, | ||
// The user provided the proxy address to verify, whereas this function is only verifying the related proxy admin. | ||
// So even if this falls back and succeeds, we want to keep any errors that might have occurred while verifying the proxy itself. | ||
false, | ||
await verifyAdminOrFallback(hardhatVerify, etherscan, adminAddress, errorReport); | ||
} | ||
} | ||
/** | ||
* Verifies a proxy admin contract by looking up an OwnershipTransferred event that should have been logged during construction | ||
* to get the owner used for its constructor. | ||
* | ||
* This is different from the verifyWithArtifactOrFallback function because the proxy admin in Contracts 5.0 is not deployed directly by the plugin, | ||
* but is deployed by the transparent proxy itself, so we cannot infer the admin's constructor arguments from the originating transaction's input bytecode. | ||
*/ | ||
async function verifyAdminOrFallback( | ||
hardhatVerify: (address: string) => Promise<any>, | ||
etherscan: Etherscan, | ||
adminAddress: string, | ||
errorReport: ErrorReport, | ||
) { | ||
const attemptVerify = async () => { | ||
let encodedOwner: string; | ||
// Get the OwnershipTransferred event when the ProxyAdmin was created, which should have the encoded owner address as its second parameter (third topic). | ||
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.`, | ||
); | ||
} catch (e: any) { | ||
if (e instanceof EventNotFound) { | ||
console.log( | ||
'Verification skipped for proxy admin - the admin address does not appear to contain a ProxyAdmin contract.', | ||
); | ||
} | ||
} else { | ||
encodedOwner = response.topics[2].replace(/^0x/, ''); | ||
} | ||
} | ||
const artifact = verifiableContracts.proxyAdmin.artifact; | ||
const deployedBytecode = await getCode(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, | ||
// The user provided the proxy address to verify, whereas this function is only verifying the related proxy admin. | ||
// So even if this falls back and succeeds, we want to keep any errors that might have occurred while verifying the proxy itself. | ||
false, | ||
); | ||
} | ||
@@ -258,3 +306,3 @@ | ||
hardhatVerify, | ||
etherscanApi, | ||
etherscan, | ||
proxyAddress, | ||
@@ -274,3 +322,3 @@ [verifiableContracts.transparentUpgradeableProxy, verifiableContracts.erc1967proxy], | ||
* @param proxyAddress The beacon proxy address | ||
* @param hardhatVerify A function that invokes the hardhat-etherscan plugin's verify command | ||
* @param hardhatVerify A function that invokes the hardhat-verify plugin's verify command | ||
* @param errorReport Accumulated verification errors | ||
@@ -287,7 +335,7 @@ */ | ||
const implAddress = await getImplementationAddressFromBeacon(provider, beaconAddress); | ||
const etherscanApi = await getEtherscanAPIConfig(hre); | ||
const etherscan = await getEtherscanInstance(hre); | ||
await fullVerifyBeacon(hre, beaconAddress, hardhatVerify, etherscanApi, errorReport); | ||
await fullVerifyBeacon(hre, beaconAddress, hardhatVerify, etherscan, errorReport); | ||
await verifyBeaconProxy(); | ||
await linkProxyWithImplementationAbi(etherscanApi, proxyAddress, implAddress, errorReport); | ||
await linkProxyWithImplementationAbi(etherscan, proxyAddress, implAddress, errorReport); | ||
@@ -299,3 +347,3 @@ async function verifyBeaconProxy() { | ||
hardhatVerify, | ||
etherscanApi, | ||
etherscan, | ||
proxyAddress, | ||
@@ -314,4 +362,4 @@ [verifiableContracts.beaconProxy], | ||
* @param beaconAddress The beacon address | ||
* @param hardhatVerify A function that invokes the hardhat-etherscan plugin's verify command | ||
* @param etherscanApi Configuration for the Etherscan API | ||
* @param hardhatVerify A function that invokes the hardhat-verify plugin's verify command | ||
* @param etherscan Etherscan instance | ||
* @param errorReport Accumulated verification errors | ||
@@ -323,3 +371,3 @@ */ | ||
hardhatVerify: (address: string) => Promise<any>, | ||
etherscanApi: EtherscanAPIConfig, | ||
etherscan: Etherscan, | ||
errorReport: ErrorReport, | ||
@@ -338,3 +386,3 @@ ) { | ||
hardhatVerify, | ||
etherscanApi, | ||
etherscan, | ||
beaconAddress, | ||
@@ -349,5 +397,5 @@ [verifiableContracts.upgradeableBeacon], | ||
/** | ||
* Runs hardhat-etherscan plugin's verify command on the given implementation address. | ||
* Runs hardhat-verify plugin's verify command on the given implementation address. | ||
* | ||
* @param hardhatVerify A function that invokes the hardhat-etherscan plugin's verify command | ||
* @param hardhatVerify A function that invokes the hardhat-verify plugin's verify command | ||
* @param implAddress The implementation address | ||
@@ -377,3 +425,3 @@ * @param errorReport Accumulated verification errors | ||
* | ||
* @param etherscanApi The Etherscan API config | ||
* @param etherscan Etherscan instance | ||
* @param address The contract address for which to look for events | ||
@@ -383,12 +431,8 @@ * @param possibleContractInfo An array of possible contract artifacts to use for verification along | ||
* @returns the VerifiableContractInfo and txHash for the first event found | ||
* @throws {EventNotFound} if none of the events were found in the contract's logs according to Etherscan. | ||
* @throws {EventOrFunctionNotFound} if none of the events were found in the contract's logs according to Etherscan. | ||
*/ | ||
async function searchEvent( | ||
etherscanApi: EtherscanAPIConfig, | ||
address: string, | ||
possibleContractInfo: VerifiableContractInfo[], | ||
) { | ||
async function searchEvent(etherscan: Etherscan, address: string, possibleContractInfo: VerifiableContractInfo[]) { | ||
for (let i = 0; i < possibleContractInfo.length; i++) { | ||
const contractInfo = possibleContractInfo[i]; | ||
const txHash = await getContractCreationTxHash(address, contractInfo.event, etherscanApi); | ||
const txHash = await getContractCreationTxHash(address, contractInfo.event, etherscan); | ||
if (txHash !== undefined) { | ||
@@ -402,33 +446,24 @@ return { contractInfo, txHash }; | ||
}); | ||
throw new EventNotFound( | ||
`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.', | ||
); | ||
throw new EventsNotFound(address, events); | ||
} | ||
/** | ||
* Verifies a contract by matching with known artifacts. | ||
* Verifies a contract using the attemptVerify function. If it fails, falls back to verify directly using the regular hardhat verify task. | ||
* | ||
* If a match was not found, falls back to verify directly using the regular hardhat verify task. | ||
* | ||
* If the fallback passes, logs as success. | ||
* If the fallback also fails, records errors for both the original and fallback attempts. | ||
* | ||
* @param hre | ||
* @param etherscanApi The Etherscan API config | ||
* @param attemptVerify A function that attempts to verify the contract. | ||
* Should throw EventOrFunctionNotFound if the contract does not contain an expected event in its logs or function in its bytecode, | ||
* or BytecodeNotMatchArtifact if the contract's bytecode does not match with the plugin's known artifact. | ||
* @param hardhatVerify A function that invokes the hardhat-verify plugin's verify command | ||
* @param address The contract address to verify | ||
* @param possibleContractInfo An array of possible contract artifacts to use for verification along | ||
* with the corresponding creation event expected in the logs. | ||
* @param errorReport Accumulated verification errors | ||
* @param convertErrorsToWarningsOnFallbackSuccess If fallback verification occurred and succeeded, whether any | ||
* previously accumulated errors should be converted into warnings in the final summary. | ||
* @throws {EventNotFound} if none of the events were found in the contract's logs according to Etherscan. | ||
*/ | ||
async function verifyWithArtifactOrFallback( | ||
hre: HardhatRuntimeEnvironment, | ||
async function attemptVerifyOrFallback( | ||
attemptVerify: () => Promise<any>, | ||
hardhatVerify: (address: string) => Promise<any>, | ||
etherscanApi: EtherscanAPIConfig, | ||
address: string, | ||
possibleContractInfo: VerifiableContractInfo[], | ||
errorReport: ErrorReport, | ||
@@ -438,6 +473,6 @@ convertErrorsToWarningsOnFallbackSuccess: boolean, | ||
try { | ||
await attemptVerifyWithCreationEvent(hre, etherscanApi, address, possibleContractInfo, errorReport); | ||
await attemptVerify(); | ||
return true; | ||
} catch (origError: any) { | ||
if (origError instanceof BytecodeNotMatchArtifact || origError instanceof EventNotFound) { | ||
if (origError instanceof BytecodeNotMatchArtifact || origError instanceof EventOrFunctionNotFound) { | ||
// Try falling back to regular hardhat verify in case the source code is available in the user's project. | ||
@@ -475,2 +510,39 @@ try { | ||
/** | ||
* Verifies a contract by matching with known artifacts. | ||
* | ||
* If a match was not found, falls back to verify directly using the regular hardhat verify task. | ||
* | ||
* If the fallback passes, logs as success. | ||
* If the fallback also fails, records errors for both the original and fallback attempts. | ||
* | ||
* @param hre | ||
* @param etherscan Etherscan instance | ||
* @param address The contract address to verify | ||
* @param possibleContractInfo An array of possible contract artifacts to use for verification along | ||
* with the corresponding creation event expected in the logs. | ||
* @param errorReport Accumulated verification errors | ||
* @param convertErrorsToWarningsOnFallbackSuccess If fallback verification occurred and succeeded, whether any | ||
* previously accumulated errors should be converted into warnings in the final summary. | ||
*/ | ||
async function verifyWithArtifactOrFallback( | ||
hre: HardhatRuntimeEnvironment, | ||
hardhatVerify: (address: string) => Promise<any>, | ||
etherscan: Etherscan, | ||
address: string, | ||
possibleContractInfo: VerifiableContractInfo[], | ||
errorReport: ErrorReport, | ||
convertErrorsToWarningsOnFallbackSuccess: boolean, | ||
) { | ||
const attemptVerify = () => | ||
attemptVerifyWithCreationEvent(hre, etherscan, address, possibleContractInfo, errorReport); | ||
return await attemptVerifyOrFallback( | ||
attemptVerify, | ||
hardhatVerify, | ||
address, | ||
errorReport, | ||
convertErrorsToWarningsOnFallbackSuccess, | ||
); | ||
} | ||
/** | ||
* Attempts to verify a contract by looking up an event that should have been logged during contract construction, | ||
@@ -482,3 +554,3 @@ * finds the txHash for that, and infers the constructor args to use for verification. | ||
* @param hre | ||
* @param etherscanApi The Etherscan API config | ||
* @param etherscan Etherscan instance | ||
* @param address The contract address to verify | ||
@@ -488,3 +560,3 @@ * @param possibleContractInfo An array of possible contract artifacts to use for verification along | ||
* @param errorReport Accumulated verification errors | ||
* @throws {EventNotFound} if none of the events were found in the contract's logs according to Etherscan. | ||
* @throws {EventOrFunctionNotFound} if none of the events were found in the contract's logs according to Etherscan. | ||
* @throws {BytecodeNotMatchArtifact} if the contract's bytecode does not match with the plugin's known artifact. | ||
@@ -494,3 +566,3 @@ */ | ||
hre: HardhatRuntimeEnvironment, | ||
etherscanApi: EtherscanAPIConfig, | ||
etherscan: Etherscan, | ||
address: string, | ||
@@ -500,3 +572,3 @@ possibleContractInfo: VerifiableContractInfo[], | ||
) { | ||
const { contractInfo, txHash } = await searchEvent(etherscanApi, address, possibleContractInfo); | ||
const { contractInfo, txHash } = await searchEvent(etherscan, address, possibleContractInfo); | ||
debug(`verifying contract ${contractInfo.artifact.contractName} at ${address}`); | ||
@@ -520,3 +592,3 @@ | ||
await verifyContractWithConstructorArgs( | ||
etherscanApi, | ||
etherscan, | ||
address, | ||
@@ -533,3 +605,3 @@ contractInfo.artifact, | ||
* | ||
* @param etherscanApi The Etherscan API config | ||
* @param etherscan Etherscan instance | ||
* @param address The address of the contract to verify | ||
@@ -540,4 +612,4 @@ * @param artifact The contract artifact to use for verification. | ||
async function verifyContractWithConstructorArgs( | ||
etherscanApi: EtherscanAPIConfig, | ||
address: any, | ||
etherscan: Etherscan, | ||
address: string, | ||
artifact: ContractArtifact, | ||
@@ -550,7 +622,5 @@ constructorArguments: string, | ||
const params = { | ||
apiKey: etherscanApi.key, | ||
contractAddress: address, | ||
sourceCode: JSON.stringify(artifactsBuildInfo.input), | ||
sourceName: artifact.sourceName, | ||
contractName: artifact.contractName, | ||
contractName: `${artifact.sourceName}:${artifact.contractName}`, | ||
compilerVersion: `v${artifactsBuildInfo.solcLongVersion}`, | ||
@@ -560,12 +630,6 @@ constructorArguments: constructorArguments, | ||
const request = toVerifyRequest(params); | ||
try { | ||
const response = await verifyContract(etherscanApi.endpoints.urls.apiURL, request); | ||
const statusRequest = toCheckStatusRequest({ | ||
apiKey: etherscanApi.key, | ||
guid: response.message, | ||
}); | ||
const status = await getVerificationStatus(etherscanApi.endpoints.urls.apiURL, statusRequest); | ||
const status = await verifyAndGetStatus(params, etherscan); | ||
if (status.isVerificationSuccess()) { | ||
if (status.isSuccess()) { | ||
console.log(`Successfully verified contract ${artifact.contractName} at ${address}.`); | ||
@@ -585,17 +649,17 @@ } else { | ||
/** | ||
* 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. | ||
* Calls the Etherscan API to look for an event that should have been emitted during construction | ||
* of the contract at the given address, and returns the result corresponding to the first event found. | ||
* | ||
* @param address The address to get the creation txhash for. | ||
* @param address The address for which to get the event response. | ||
* @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 | ||
* @param etherscan Etherscan instance | ||
* @returns The event response, or undefined if not found or if | ||
* the address is not a contract. | ||
* @throws {UpgradesError} if the Etherscan API returned with not OK status | ||
*/ | ||
async function getContractCreationTxHash( | ||
async function getEventResponse( | ||
address: string, | ||
topic: string, | ||
etherscanApi: EtherscanAPIConfig, | ||
): Promise<any> { | ||
etherscan: Etherscan, | ||
): Promise<EtherscanEventResponse | undefined> { | ||
const params = { | ||
@@ -610,7 +674,7 @@ module: 'logs', | ||
const responseBody = await callEtherscanApi(etherscanApi, params); | ||
const responseBody = await callEtherscanApi(etherscan, params); | ||
if (responseBody.status === RESPONSE_OK) { | ||
const result = responseBody.result; | ||
return result[0].transactionHash; // get the txhash from the first instance of this event | ||
const result = responseBody.result as EtherscanEventResponse[]; | ||
return result[0]; | ||
} else if (responseBody.message === 'No records found' || responseBody.message === 'No logs found') { | ||
@@ -628,5 +692,25 @@ debug(`no result found for event topic ${topic} at address ${address}`); | ||
/** | ||
* 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 etherscan Etherscan instance | ||
* @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 | ||
*/ | ||
async function getContractCreationTxHash(address: string, topic: string, etherscan: Etherscan): Promise<any> { | ||
const eventResponse = await getEventResponse(address, topic, etherscan); | ||
if (eventResponse === undefined) { | ||
return undefined; | ||
} else { | ||
return eventResponse.transactionHash; | ||
} | ||
} | ||
/** | ||
* Calls the Etherscan API to link a proxy with its implementation ABI. | ||
* | ||
* @param etherscanApi The Etherscan API config | ||
* @param etherscan Etherscan instance | ||
* @param proxyAddress The proxy address | ||
@@ -636,3 +720,3 @@ * @param implAddress The implementation address | ||
async function linkProxyWithImplementationAbi( | ||
etherscanApi: EtherscanAPIConfig, | ||
etherscan: Etherscan, | ||
proxyAddress: string, | ||
@@ -649,12 +733,12 @@ implAddress: string, | ||
}; | ||
let responseBody = await callEtherscanApi(etherscanApi, params); | ||
let responseBody = await callEtherscanApi(etherscan, params); | ||
if (responseBody.status === RESPONSE_OK) { | ||
// initial call was OK, but need to send a status request using the returned guid to get the actual verification status | ||
const guid = responseBody.result; | ||
responseBody = await checkProxyVerificationStatus(etherscanApi, guid); | ||
const guid = responseBody.result as string; | ||
responseBody = await checkProxyVerificationStatus(etherscan, guid); | ||
while (responseBody.result === 'Pending in queue') { | ||
await delay(3000); | ||
responseBody = await checkProxyVerificationStatus(etherscanApi, guid); | ||
responseBody = await checkProxyVerificationStatus(etherscan, guid); | ||
} | ||
@@ -677,10 +761,10 @@ } | ||
async function checkProxyVerificationStatus(etherscanApi: EtherscanAPIConfig, guid: string) { | ||
async function checkProxyVerificationStatus(etherscan: Etherscan, guid: string) { | ||
const checkProxyVerificationParams = { | ||
module: 'contract', | ||
action: 'checkproxyverification', | ||
apikey: etherscanApi.key, | ||
apikey: etherscan.apiKey, | ||
guid: guid, | ||
}; | ||
return await callEtherscanApi(etherscanApi, checkProxyVerificationParams); | ||
return await callEtherscanApi(etherscan, checkProxyVerificationParams); | ||
} | ||
@@ -687,0 +771,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
393220
203
5976
14
12
4
+ Addedethereumjs-util@^7.1.5
+ Addedundici@^5.28.2
+ Added@adraffy/ens-normalize@1.10.1(transitive)
+ Added@noble/curves@1.2.0(transitive)
+ Added@noble/hashes@1.3.2(transitive)
+ Added@nomicfoundation/hardhat-ethers@3.0.8(transitive)
+ Added@nomicfoundation/hardhat-verify@2.0.10(transitive)
+ Added@openzeppelin/defender-admin-client@1.54.6(transitive)
+ Added@openzeppelin/defender-sdk-base-client@1.14.4(transitive)
+ Added@openzeppelin/defender-sdk-deploy-client@1.14.4(transitive)
+ Added@types/node@18.15.13(transitive)
+ Addedaes-js@4.0.0-beta.5(transitive)
+ Addedethers@6.13.2(transitive)
+ Addedlodash.clonedeep@4.5.0(transitive)
+ Addedlodash.isequal@4.5.0(transitive)
+ Addedtslib@2.4.0(transitive)
+ Addedws@8.17.1(transitive)
- Removed@nomiclabs/hardhat-ethers@2.2.3(transitive)
- Removed@nomiclabs/hardhat-etherscan@3.1.8(transitive)
- Removed@openzeppelin/platform-deploy-client@0.8.0(transitive)
- Removedaxios@0.21.4(transitive)