@openzeppelin/hardhat-upgrades
Version:
[](https://docs.openzeppelin.com/upgrades-plugins/hardhat-upgrades) [](https://www.npmjs.org/package/@openzeppelin/h
231 lines • 12.2 kB
JavaScript
import { parseFullyQualifiedName } from 'hardhat/utils/contract-names';
import { getContractNameAndRunValidation, UpgradesError } from '@openzeppelin/upgrades-core';
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const artifactsBuildInfo = require('@openzeppelin/upgrades-core/artifacts/build-info-v5.json');
const ERC1967Proxy = require('@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.json');
const BeaconProxy = require('@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/beacon/BeaconProxy.sol/BeaconProxy.json');
const UpgradeableBeacon = require('@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.json');
const TransparentUpgradeableProxy = require('@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts-v5/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json');
import { getNetwork, parseTxOverrides } from './utils.js';
import debug from '../utils/debug.js';
import { getDeployData } from '../utils/deploy-impl.js';
import { ContractSourceNotFoundError } from '@openzeppelin/upgrades-core';
import { getDeployClient } from './client.js';
import { getCombinedBuildInfo } from '../utils/artifacts.js';
const deployableProxyContracts = [ERC1967Proxy, BeaconProxy, UpgradeableBeacon, TransparentUpgradeableProxy];
/**
* Removes the 'project/' prefix from a fully qualified name if present.
* In Hardhat 3, validation data uses 'project/' prefix for local contracts
* (e.g., "project/contracts/Greeter.sol:Greeter"), but the artifact system
* expects names without this prefix (e.g., "contracts/Greeter.sol:Greeter").
*/
function removeProjectPrefix(fullyQualifiedName) {
const prefix = 'project/';
return fullyQualifiedName.startsWith(prefix) ? fullyQualifiedName.slice(prefix.length) : fullyQualifiedName;
}
const ORIGIN_HARDHAT = 'Hardhat';
export async function defenderDeploy(hre, factory, opts, ...args) {
// Create connection if not available in context
const connection = await hre.network.create();
const client = getDeployClient(hre);
// Override constructor arguments in options with the ones passed as arguments to this function.
// The ones in the options are for implementation contracts only, while this function
// can be used to deploy proxies as well.
const contractInfo = await getContractInfo(hre, factory, { ...opts, constructorArgs: args }, connection);
const network = await getNetwork(hre, connection);
debug(`Network ${network}`);
const verifySourceCode = opts.verifySourceCode ?? true;
debug(`Verify source code: ${verifySourceCode}`);
if (opts.salt !== undefined) {
debug(`Salt: ${opts.salt}`);
}
if (opts.licenseType !== undefined) {
if (opts.verifySourceCode === false) {
throw new UpgradesError('The `licenseType` option cannot be used when the `verifySourceCode` option is `false`');
}
else if (opts.skipLicenseType) {
throw new UpgradesError('The `licenseType` option cannot be used when the `skipLicenseType` option is `true`');
}
}
let licenseType = undefined;
if (verifySourceCode) {
if (opts.licenseType !== undefined) {
licenseType = opts.licenseType;
debug(`licenseType option: ${licenseType}`);
}
else if (!opts.skipLicenseType) {
const spdxIdentifier = getSpdxLicenseIdentifier(contractInfo);
debug(`SPDX license identifier from metadata: ${spdxIdentifier}`);
if (spdxIdentifier !== undefined) {
licenseType = toLicenseType(spdxIdentifier, contractInfo);
debug(`licenseType inferred: ${licenseType}`);
}
}
}
const deploymentRequest = {
contractName: contractInfo.contractName,
contractPath: contractInfo.sourceName,
network: network,
artifactPayload: JSON.stringify(contractInfo.buildInfo),
licenseType: licenseType,
constructorBytecode: contractInfo.constructorBytecode,
verifySourceCode: verifySourceCode,
relayerId: opts.relayerId,
salt: opts.salt,
createFactoryAddress: opts.createFactoryAddress,
txOverrides: parseTxOverrides(opts.txOverrides),
libraries: contractInfo.libraries,
metadata: opts.metadata,
origin: ORIGIN_HARDHAT,
};
let deploymentResponse;
try {
deploymentResponse = await client.deployContract(deploymentRequest);
}
catch (e) {
if (e.response?.data?.message?.includes('licenseType should be equal to one of the allowed values')) {
throw new UpgradesError(`The licenseType option "${licenseType}" is not valid for block explorer verification.`, () => 'See https://etherscan.io/contract-license-types for supported values and use the string found in brackets, e.g. "MIT"');
}
else {
throw e;
}
}
// For EOA or Safe deployments, address and/or txHash are not known until the deployment is completed.
// In this case, prompt the user to submit the deployment in Defender, and wait for it to be completed.
if (deploymentResponse.address === undefined || deploymentResponse.txHash === undefined) {
console.log(`ACTION REQUIRED: Go to https://defender.openzeppelin.com/v2/#/deploy to submit the pending deployment.`);
console.log(`The process will continue automatically when the pending deployment is completed.`);
console.log(`Waiting for pending deployment of contract ${contractInfo.contractName} with deployment id ${deploymentResponse.deploymentId}...`);
const pollInterval = opts.pollingInterval ?? 5e3;
while (deploymentResponse.address === undefined || deploymentResponse.txHash === undefined) {
debug(`Waiting for deployment id ${deploymentResponse.deploymentId} to return address and txHash...`);
await new Promise(resolve => setTimeout(resolve, pollInterval));
deploymentResponse = await client.getDeployedContract(deploymentResponse.deploymentId);
}
}
const { ethers } = connection;
const txResponse = (await ethers.provider.getTransaction(deploymentResponse.txHash)) ?? undefined;
const checksumAddress = ethers.getAddress(deploymentResponse.address);
return {
address: checksumAddress,
txHash: deploymentResponse.txHash,
deployTransaction: txResponse,
remoteDeploymentId: deploymentResponse.deploymentId,
};
}
async function getContractInfo(hre, factory, opts, connection) {
let fullContractName, runValidation;
let libraries;
let constructorBytecode;
try {
// Get fully qualified contract name and link references from validations
const deployData = await getDeployData(hre, factory, opts, connection);
constructorBytecode = deployData.encodedArgs;
[fullContractName, runValidation] = getContractNameAndRunValidation(deployData.validations, deployData.version);
debug(`Contract ${fullContractName}`);
// Get externally linked libraries
const linkReferences = runValidation[fullContractName].linkReferences;
for (const ref in linkReferences) {
const linkedBytes = factory.bytecode.slice(2);
const start = linkReferences[ref].start * 2;
const length = linkReferences[ref].length * 2;
const linkFullyQualifiedName = `${linkReferences[ref].src}:${linkReferences[ref].name}`;
const linkAddress = `0x${linkedBytes.substring(start, start + length)}`;
libraries ??= {};
libraries[linkFullyQualifiedName] = linkAddress;
}
debug(`Libraries: ${JSON.stringify(libraries, null, 2)}`);
}
catch (e) {
if (e instanceof ContractSourceNotFoundError) {
// Proxy contracts would not be found in the validations, so try to get these from the plugin's precompiled artifacts.
for (const artifact of deployableProxyContracts) {
if (artifact.bytecode === factory.bytecode) {
const sourceName = artifact.sourceName;
const contractName = artifact.contractName;
const buildInfo = artifactsBuildInfo;
debug(`Proxy contract ${sourceName}:${contractName}`);
return {
sourceName,
contractName,
buildInfo,
constructorBytecode: factory.interface.encodeDeploy(opts.constructorArgs),
};
}
}
}
// If nothing else worked, re-throw error about the contract not being found.
throw e;
}
// Remove 'project/' prefix for artifact resolution (Hardhat 3 compatibility)
const artifactName = removeProjectPrefix(fullContractName);
const { sourceName, contractName } = parseFullyQualifiedName(artifactName);
// Read the artifact to get inputSourceName which is needed for buildInfo access
const artifact = await hre.artifacts.readArtifact(artifactName);
const inputSourceName = artifact.inputSourceName || sourceName; // fallback for Hardhat 2
// Get the build-info file corresponding to the fully qualified contract name
const buildInfo = await getCombinedBuildInfo(hre.artifacts, artifactName);
if (buildInfo === undefined) {
throw new UpgradesError(`Could not get Hardhat compilation artifact for contract ${fullContractName}`, () => `Run \`npx hardhat compile\``);
}
return { sourceName, contractName, buildInfo, libraries, constructorBytecode, inputSourceName };
}
/**
* Get the SPDX license identifier from the contract metadata without validating it.
*/
function getSpdxLicenseIdentifier(contractInfo) {
// Use inputSourceName for buildInfo access (Hardhat 3 has project/ prefix), fallback to sourceName
const buildInfoSourceName = contractInfo.inputSourceName || contractInfo.sourceName;
const compilerOutput = contractInfo.buildInfo.output.contracts[buildInfoSourceName][contractInfo.contractName];
const metadataString = compilerOutput.metadata;
if (metadataString === undefined) {
throw new UpgradesError('License type could not be determined from contract metadata', () => 'Enable metadata output in your compiler settings.');
}
const metadata = JSON.parse(metadataString);
// Metadata sources are also keyed by inputSourceName in Hardhat 3
return metadata.sources[buildInfoSourceName].license;
}
/**
* Infers a SourceCodeLicense from an SPDX license identifier.
*/
function toLicenseType(spdxIdentifier, contractInfo) {
switch (spdxIdentifier) {
case 'UNLICENSED':
return 'None';
case 'Unlicense':
return 'Unlicense';
case 'MIT':
return 'MIT';
case 'GPL-2.0-only':
case 'GPL-2.0-or-later':
return 'GNU GPLv2';
case 'GPL-3.0-only':
case 'GPL-3.0-or-later':
return 'GNU GPLv3';
case 'LGPL-2.1-only':
case 'LGPL-2.1-or-later':
return 'GNU LGPLv2.1';
case 'LGPL-3.0-only':
case 'LGPL-3.0-or-later':
return 'GNU LGPLv3';
case 'BSD-2-Clause':
return 'BSD-2-Clause';
case 'BSD-3-Clause':
return 'BSD-3-Clause';
case 'MPL-2.0':
return 'MPL-2.0';
case 'OSL-3.0':
return 'OSL-3.0';
case 'Apache-2.0':
return 'Apache-2.0';
case 'AGPL-3.0-only':
case 'AGPL-3.0-or-later':
return 'GNU AGPLv3';
case 'BUSL-1.1':
return 'BSL 1.1';
default:
throw new UpgradesError(`SPDX license identifier ${spdxIdentifier} in ${contractInfo.sourceName} does not look like a supported license for block explorer verification.`, () => `Use the \`licenseType\` option to specify a license type, or set the \`skipLicenseType\` option to \`true\` to skip.`);
}
}
//# sourceMappingURL=deploy.js.map