diff --git a/node_modules/@openzeppelin/hardhat-upgrades/dist/deploy-proxy.js b/node_modules/@openzeppelin/hardhat-upgrades/dist/deploy-proxy.js index 01e2cd9..a278d26 100644 --- a/node_modules/@openzeppelin/hardhat-upgrades/dist/deploy-proxy.js +++ b/node_modules/@openzeppelin/hardhat-upgrades/dist/deploy-proxy.js @@ -15,7 +15,64 @@ function makeDeployProxy(hre, defenderModule) { opts = (0, utils_2.enableDefender)(hre, defenderModule, opts); const { provider } = hre.network; const manifest = await upgrades_core_1.Manifest.forNetwork(provider); - const { impl, kind } = await (0, utils_1.deployProxyImpl)(hre, ImplFactory, opts); + let impl; + let kind = opts.kind; + if (opts.create2Factory?.implementationAddress) { + const { getDeployData } = await Promise.resolve().then(() => require('./utils/deploy-impl')); + const deployData = await getDeployData(hre, ImplFactory, opts); + const { validateProxyImpl } = await Promise.resolve().then(() => require('./utils/validate-impl')); + await validateProxyImpl(deployData, opts); + if (opts.kind === undefined) { + throw new Error('Broken invariant: Proxy kind is undefined'); + } + kind = opts.kind; + impl = opts.create2Factory.implementationAddress; + } + else if (opts.create2Factory && opts.create2Factory.deployImpl !== false) { + const { getDeployData } = await Promise.resolve().then(() => require('./utils/deploy-impl')); + const deployData = await getDeployData(hre, ImplFactory, opts); + const { validateProxyImpl } = await Promise.resolve().then(() => require('./utils/validate-impl')); + await validateProxyImpl(deployData, opts); + if (opts.kind === undefined) { + throw new Error('Broken invariant: Proxy kind is undefined'); + } + kind = opts.kind; + const implSalt = opts.create2Factory.implSalt; + if (!implSalt) { + throw new Error('create2Factory implementation deployment requires an implementation salt'); + } + const implBytecode = ImplFactory.bytecode; + const initCode = hre.ethers.concat([implBytecode, '0x']); + const expectedAddress = hre.ethers.getCreate2Address(opts.create2Factory.address, implSalt, hre.ethers.keccak256(initCode)); + const existingCode = await hre.ethers.provider.getCode(expectedAddress); + const implExists = existingCode !== '0x'; + const deployment = await (0, upgrades_core_1.fetchOrDeployGetDeployment)(deployData.version, deployData.provider, async () => { + const abi = ImplFactory.interface.format(true); + if (implExists) { + return { + abi, + layout: deployData.layout, + address: expectedAddress, + txHash: undefined, + }; + } + const implDeployment = await (0, utils_1.deployViaCreate2Factory)(hre, opts.create2Factory.address, implSalt, implBytecode, '0x'); + return { + abi, + layout: deployData.layout, + address: implDeployment.address, + txHash: implDeployment.txHash, + deployTransaction: implDeployment.deployTransaction, + remoteDeploymentId: implDeployment.remoteDeploymentId || '', + }; + }, opts, false); + impl = deployment.address; + } + else { + const result = await (0, utils_1.deployProxyImpl)(hre, ImplFactory, opts); + impl = result.impl; + kind = result.kind; + } const contractInterface = ImplFactory.interface; const data = (0, utils_1.getInitializerData)(contractInterface, args, opts.initializer); const deployFn = opts.deployFunction || utils_1.deploy; @@ -34,6 +91,39 @@ function makeDeployProxy(hre, defenderModule) { } } const signer = (0, utils_1.getSigner)(ImplFactory.runner); + const deployCreate2Proxy = async (artifactName, constructorTypes, constructorValues) => { + if (!opts.create2Factory) { + throw new Error('create2Factory is required'); + } + const proxyArtifact = await hre.artifacts.readArtifact(artifactName); + const proxyBytecode = proxyArtifact.bytecode; + const constructorArgs = hre.ethers.AbiCoder.defaultAbiCoder().encode(constructorTypes, constructorValues); + const proxyInitCode = hre.ethers.concat([proxyBytecode, constructorArgs]); + const expectedProxyAddress = hre.ethers.getCreate2Address(opts.create2Factory.address, opts.create2Factory.salt, hre.ethers.keccak256(proxyInitCode)); + const existingProxyCode = await hre.ethers.provider.getCode(expectedProxyAddress); + if (existingProxyCode !== '0x') { + return { + kind, + address: expectedProxyAddress, + txHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + remoteDeploymentId: '', + }; + } + if (opts.create2Factory.multicall) { + const deployerAddress = await signer.getAddress(); + return { + kind, + ...(await (0, utils_1.deployViaMulticall)(hre, opts.create2Factory.multicall, opts.create2Factory.address, opts.create2Factory.salt, proxyBytecode, constructorArgs, expectedProxyAddress, data, deployerAddress, opts.create2Factory.transferOwnership === true, opts.create2Factory.postDeployCalls || [])), + }; + } + if (opts.create2Factory.postDeployCalls?.length) { + throw new Error('create2Factory.postDeployCalls requires create2Factory.multicall'); + } + return { + kind, + ...(await (0, utils_1.deployViaCreate2Factory)(hre, opts.create2Factory.address, opts.create2Factory.salt, proxyBytecode, constructorArgs)), + }; + }; let proxyDeployment; switch (kind) { case 'beacon': { @@ -44,7 +134,12 @@ function makeDeployProxy(hre, defenderModule) { throw new upgrades_core_1.InitialOwnerUnsupportedKindError(kind); } const ProxyFactory = opts.proxyFactory || (await (0, utils_1.getProxyFactory)(hre, signer)); - proxyDeployment = Object.assign({ kind }, await deployFn(hre, opts, ProxyFactory, impl, data)); + if (opts.create2Factory) { + proxyDeployment = await deployCreate2Proxy('@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol:ERC1967Proxy', ['address', 'bytes'], [impl, opts.create2Factory.multicall ? '0x' : data]); + } + else { + proxyDeployment = Object.assign({ kind }, await deployFn(hre, opts, ProxyFactory, impl, data)); + } break; } case 'transparent': { @@ -53,7 +148,12 @@ function makeDeployProxy(hre, defenderModule) { throw new upgrades_core_1.UpgradesError('`initialOwner` must not be a ProxyAdmin contract.', () => `If the contract at address ${initialOwner} is not a ProxyAdmin contract and you are sure that this contract is able to call functions on an actual ProxyAdmin, skip this check with the \`unsafeSkipProxyAdminCheck\` option.`); } const TransparentUpgradeableProxyFactory = opts.proxyFactory || (await (0, utils_1.getTransparentUpgradeableProxyFactory)(hre, signer)); - proxyDeployment = Object.assign({ kind }, await deployFn(hre, opts, TransparentUpgradeableProxyFactory, impl, initialOwner, data)); + if (opts.create2Factory) { + proxyDeployment = await deployCreate2Proxy('@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol:TransparentUpgradeableProxy', ['address', 'address', 'bytes'], [impl, initialOwner, opts.create2Factory.multicall ? '0x' : data]); + } + else { + proxyDeployment = Object.assign({ kind }, await deployFn(hre, opts, TransparentUpgradeableProxyFactory, impl, initialOwner, data)); + } break; } } diff --git a/node_modules/@openzeppelin/hardhat-upgrades/dist/utils/deploy.d.ts b/node_modules/@openzeppelin/hardhat-upgrades/dist/utils/deploy.d.ts index da66d9a..403db3f 100644 --- a/node_modules/@openzeppelin/hardhat-upgrades/dist/utils/deploy.d.ts +++ b/node_modules/@openzeppelin/hardhat-upgrades/dist/utils/deploy.d.ts @@ -9,4 +9,10 @@ export type EthersOrDefenderDeployment = Required & DeployTransactio export type DefenderDeployment = Required & DeployTransaction; export type EthersDeployment = Required & RemoteDeploymentId; export declare function deploy(hre: HardhatRuntimeEnvironment, opts: UpgradeOptions & EthersDeployOptions & DefenderDeployOptions, factory: ContractFactory, ...args: unknown[]): Promise; +export declare function deployViaCreate2Factory(hre: HardhatRuntimeEnvironment, factoryAddress: string, salt: string, proxyBytecode: string, constructorArgs: string): Promise; +export declare function deployViaMulticall(hre: HardhatRuntimeEnvironment, multicallAddress: string, factoryAddress: string, salt: string, proxyBytecode: string, constructorArgs: string, expectedProxyAddress: string, initData: string, deployerAddress?: string, shouldTransferOwnership?: boolean, postDeployCalls?: Array<{ + target?: string; + data: string; + value?: string; +}>): Promise; //# sourceMappingURL=deploy.d.ts.map \ No newline at end of file diff --git a/node_modules/@openzeppelin/hardhat-upgrades/dist/utils/deploy.js b/node_modules/@openzeppelin/hardhat-upgrades/dist/utils/deploy.js index 46c2c7f..a10f82b 100644 --- a/node_modules/@openzeppelin/hardhat-upgrades/dist/utils/deploy.js +++ b/node_modules/@openzeppelin/hardhat-upgrades/dist/utils/deploy.js @@ -1,6 +1,8 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.deploy = deploy; +exports.deployViaCreate2Factory = deployViaCreate2Factory; +exports.deployViaMulticall = deployViaMulticall; const deploy_1 = require("../defender/deploy"); async function deploy(hre, opts, factory, ...args) { if (opts?.useDefenderDeploy) { @@ -23,4 +25,99 @@ async function ethersDeploy(factory, ...args) { const txHash = deployTransaction.hash; return { address, txHash, deployTransaction }; } +async function deployViaCreate2Factory(hre, factoryAddress, salt, proxyBytecode, constructorArgs) { + const [deployer] = await hre.ethers.getSigners(); + const initCode = hre.ethers.concat([proxyBytecode, constructorArgs]); + const expectedAddress = hre.ethers.getCreate2Address(factoryAddress, salt, hre.ethers.keccak256(initCode)); + const factoryContract = await hre.ethers.getContractAt([ + 'function deploy(bytes32 salt, bytes memory initCode, bytes memory data, uint256 create2ForwardValue, uint256 callForwardValue) external payable returns (address deployed)', + 'event Deployed(address indexed addr)', + ], factoryAddress, deployer); + const tx = await factoryContract.deploy(salt, initCode, '0x', 0, 0, { value: 0 }); + const receipt = await tx.wait(); + if (!receipt) { + throw new Error('Deployment transaction failed'); + } + let actualAddress = expectedAddress; + const deployedEvent = receipt.logs.find((log) => { + try { + const iface = new hre.ethers.Interface(['event Deployed(address indexed addr)']); + const parsed = iface.parseLog(log); + return parsed?.name === 'Deployed'; + } + catch { + return false; + } + }); + if (deployedEvent) { + const iface = new hre.ethers.Interface(['event Deployed(address indexed addr)']); + const parsed = iface.parseLog(deployedEvent); + actualAddress = parsed?.args[0]; + } + if (actualAddress.toLowerCase() !== expectedAddress.toLowerCase()) { + console.warn(`Warning: Actual address ${actualAddress} does not match expected address ${expectedAddress}`); + } + return { + address: actualAddress, + txHash: receipt.hash, + deployTransaction: tx, + remoteDeploymentId: '', + }; +} +async function deployViaMulticall(hre, multicallAddress, factoryAddress, salt, proxyBytecode, constructorArgs, expectedProxyAddress, initData, deployerAddress, shouldTransferOwnership, postDeployCalls = []) { + const [deployer] = await hre.ethers.getSigners(); + const initCode = hre.ethers.concat([proxyBytecode, constructorArgs]); + const factoryInterface = new hre.ethers.Interface([ + 'function deploy(bytes32 salt, bytes memory initCode, bytes memory data, uint256 create2ForwardValue, uint256 callForwardValue) external payable returns (address deployed)', + ]); + const deployCallData = factoryInterface.encodeFunctionData('deploy', [salt, initCode, '0x', 0, 0]); + const multicall = await hre.ethers.getContractAt([ + 'function aggregate3(tuple(address target, bool allowFailure, bytes callData)[] calldata calls) external payable returns (tuple(bool success, bytes returnData)[] memory returnData)', + ], multicallAddress, deployer); + const calls = [ + { + target: factoryAddress, + allowFailure: false, + callData: deployCallData, + }, + ]; + if (initData && initData !== '0x') { + calls.push({ + target: expectedProxyAddress, + allowFailure: false, + callData: initData, + }); + } + for (const postDeployCall of postDeployCalls || []) { + if (postDeployCall.value && postDeployCall.value !== '0' && postDeployCall.value !== '0x0') { + throw new Error('create2Factory.postDeployCalls does not support non-zero value with aggregate3'); + } + calls.push({ + target: !postDeployCall.target || postDeployCall.target === '$proxy' + ? expectedProxyAddress + : postDeployCall.target, + allowFailure: false, + callData: postDeployCall.data, + }); + } + if (shouldTransferOwnership && deployerAddress) { + const ownableInterface = new hre.ethers.Interface(['function transferOwnership(address newOwner)']); + calls.push({ + target: expectedProxyAddress, + allowFailure: false, + callData: ownableInterface.encodeFunctionData('transferOwnership', [deployerAddress]), + }); + } + const tx = await multicall.aggregate3(calls, { value: 0 }); + const receipt = await tx.wait(); + if (!receipt) { + throw new Error('Multicall deployment transaction failed'); + } + return { + address: expectedProxyAddress, + txHash: receipt.hash, + deployTransaction: tx, + remoteDeploymentId: '', + }; +} //# sourceMappingURL=deploy.js.map \ No newline at end of file diff --git a/node_modules/@openzeppelin/hardhat-upgrades/dist/utils/options.d.ts b/node_modules/@openzeppelin/hardhat-upgrades/dist/utils/options.d.ts index ed9dd7a..7397e31 100644 --- a/node_modules/@openzeppelin/hardhat-upgrades/dist/utils/options.d.ts +++ b/node_modules/@openzeppelin/hardhat-upgrades/dist/utils/options.d.ts @@ -14,6 +14,23 @@ export type DeployFactoryOpts = { * Allows to customize the deploy function used instead of utils/deploy.ts:deploy */ deployFunction?: () => Promise; + /** + * Use a CREATE2 factory for proxy deployment. + */ + create2Factory?: { + address: string; + salt: string; + implementationAddress?: string; + implSalt?: string; + deployImpl?: boolean; + multicall?: string; + transferOwnership?: boolean; + postDeployCalls?: Array<{ + target?: string; + data: string; + value?: string; + }>; + }; }; /** * Options for functions that can deploy an implementation contract. diff --git a/node_modules/@openzeppelin/hardhat-upgrades/src/deploy-proxy.ts b/node_modules/@openzeppelin/hardhat-upgrades/src/deploy-proxy.ts index b912102..f70ec44 100644 --- a/node_modules/@openzeppelin/hardhat-upgrades/src/deploy-proxy.ts +++ b/node_modules/@openzeppelin/hardhat-upgrades/src/deploy-proxy.ts @@ -3,6 +3,7 @@ import type { ContractFactory } from 'ethers'; import { Manifest, + fetchOrDeployGetDeployment, logWarning, ProxyDeployment, BeaconProxyUnsupportedError, @@ -21,11 +22,15 @@ import { deployProxyImpl, getInitializerData, getSigner, + deployViaCreate2Factory, + deployViaMulticall, } from './utils'; import { enableDefender } from './defender/utils'; import { getContractInstance } from './utils/contract-instance'; import { getInitialOwner } from './utils/initial-owner'; import { ContractTypeOfFactory } from './type-extensions'; +import { getDeployData } from './utils/deploy-impl'; +import { validateProxyImpl } from './utils/validate-impl'; export interface DeployFunction { ( @@ -52,7 +57,84 @@ export function makeDeployProxy(hre: HardhatRuntimeEnvironment, defenderModule: const { provider } = hre.network; const manifest = await Manifest.forNetwork(provider); - const { impl, kind } = await deployProxyImpl(hre, ImplFactory, opts); + let impl: string; + let kind = opts.kind; + + if (opts.create2Factory?.implementationAddress) { + const deployData = await getDeployData(hre, ImplFactory, opts); + await validateProxyImpl(deployData, opts); + + if (opts.kind === undefined) { + throw new Error('Broken invariant: Proxy kind is undefined'); + } + kind = opts.kind; + impl = opts.create2Factory.implementationAddress; + } else if (opts.create2Factory && opts.create2Factory.deployImpl !== false) { + const deployData = await getDeployData(hre, ImplFactory, opts); + await validateProxyImpl(deployData, opts); + + if (opts.kind === undefined) { + throw new Error('Broken invariant: Proxy kind is undefined'); + } + kind = opts.kind; + + const implSalt = opts.create2Factory.implSalt; + if (!implSalt) { + throw new Error('create2Factory implementation deployment requires an implementation salt'); + } + + const implBytecode = ImplFactory.bytecode; + const initCode = hre.ethers.concat([implBytecode, '0x']); + const expectedAddress = hre.ethers.getCreate2Address( + opts.create2Factory.address, + implSalt, + hre.ethers.keccak256(initCode), + ); + const existingCode = await hre.ethers.provider.getCode(expectedAddress); + const implExists = existingCode !== '0x'; + + const deployment = await fetchOrDeployGetDeployment( + deployData.version, + deployData.provider, + async () => { + const abi = ImplFactory.interface.format(true); + + if (implExists) { + return { + abi, + layout: deployData.layout, + address: expectedAddress, + txHash: undefined, + }; + } + + const implDeployment = await deployViaCreate2Factory( + hre, + opts.create2Factory!.address, + implSalt, + implBytecode, + '0x', + ); + + return { + abi, + layout: deployData.layout, + address: implDeployment.address, + txHash: implDeployment.txHash, + deployTransaction: implDeployment.deployTransaction, + remoteDeploymentId: implDeployment.remoteDeploymentId || '', + }; + }, + opts, + false, + ); + + impl = deployment.address; + } else { + const result = await deployProxyImpl(hre, ImplFactory, opts); + impl = result.impl; + kind = result.kind; + } const contractInterface = ImplFactory.interface; const data = getInitializerData(contractInterface, args, opts.initializer); @@ -73,6 +155,70 @@ export function makeDeployProxy(hre: HardhatRuntimeEnvironment, defenderModule: } const signer = getSigner(ImplFactory.runner); + const deployCreate2Proxy = async ( + artifactName: string, + constructorTypes: string[], + constructorValues: unknown[], + ): Promise & DeployTransaction & RemoteDeploymentId> => { + if (!opts.create2Factory) { + throw new Error('create2Factory is required'); + } + + const proxyArtifact = await hre.artifacts.readArtifact(artifactName); + const proxyBytecode = proxyArtifact.bytecode; + const constructorArgs = hre.ethers.AbiCoder.defaultAbiCoder().encode(constructorTypes, constructorValues); + const proxyInitCode = hre.ethers.concat([proxyBytecode, constructorArgs]); + const expectedProxyAddress = hre.ethers.getCreate2Address( + opts.create2Factory.address, + opts.create2Factory.salt, + hre.ethers.keccak256(proxyInitCode), + ); + const existingProxyCode = await hre.ethers.provider.getCode(expectedProxyAddress); + + if (existingProxyCode !== '0x') { + return { + kind: kind!, + address: expectedProxyAddress, + txHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + remoteDeploymentId: '', + }; + } + + if (opts.create2Factory.multicall) { + const deployerAddress = await signer.getAddress(); + return { + kind: kind!, + ...(await deployViaMulticall( + hre, + opts.create2Factory.multicall, + opts.create2Factory.address, + opts.create2Factory.salt, + proxyBytecode, + constructorArgs, + expectedProxyAddress, + data, + deployerAddress, + opts.create2Factory.transferOwnership === true, + opts.create2Factory.postDeployCalls || [], + )), + }; + } + + if (opts.create2Factory.postDeployCalls?.length) { + throw new Error('create2Factory.postDeployCalls requires create2Factory.multicall'); + } + + return { + kind: kind!, + ...(await deployViaCreate2Factory( + hre, + opts.create2Factory.address, + opts.create2Factory.salt, + proxyBytecode, + constructorArgs, + )), + }; + }; let proxyDeployment: Required & DeployTransaction & RemoteDeploymentId; switch (kind) { @@ -86,7 +232,15 @@ export function makeDeployProxy(hre: HardhatRuntimeEnvironment, defenderModule: } const ProxyFactory = opts.proxyFactory || (await getProxyFactory(hre, signer)); - proxyDeployment = Object.assign({ kind }, await deployFn(hre, opts, ProxyFactory, impl, data)); + if (opts.create2Factory) { + proxyDeployment = await deployCreate2Proxy( + '@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol:ERC1967Proxy', + ['address', 'bytes'], + [impl, opts.create2Factory.multicall ? '0x' : data], + ); + } else { + proxyDeployment = Object.assign({ kind }, await deployFn(hre, opts, ProxyFactory, impl, data)); + } break; } @@ -103,10 +257,18 @@ export function makeDeployProxy(hre: HardhatRuntimeEnvironment, defenderModule: const TransparentUpgradeableProxyFactory = opts.proxyFactory || (await getTransparentUpgradeableProxyFactory(hre, signer)); - proxyDeployment = Object.assign( - { kind }, - await deployFn(hre, opts, TransparentUpgradeableProxyFactory, impl, initialOwner, data), - ); + if (opts.create2Factory) { + proxyDeployment = await deployCreate2Proxy( + '@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol:TransparentUpgradeableProxy', + ['address', 'address', 'bytes'], + [impl, initialOwner, opts.create2Factory.multicall ? '0x' : data], + ); + } else { + proxyDeployment = Object.assign( + { kind }, + await deployFn(hre, opts, TransparentUpgradeableProxyFactory, impl, initialOwner, data), + ); + } break; } } diff --git a/node_modules/@openzeppelin/hardhat-upgrades/src/utils/deploy.ts b/node_modules/@openzeppelin/hardhat-upgrades/src/utils/deploy.ts index 9543317..28f0866 100644 --- a/node_modules/@openzeppelin/hardhat-upgrades/src/utils/deploy.ts +++ b/node_modules/@openzeppelin/hardhat-upgrades/src/utils/deploy.ts @@ -45,3 +45,134 @@ async function ethersDeploy( return { address, txHash, deployTransaction }; } + +export async function deployViaCreate2Factory( + hre: HardhatRuntimeEnvironment, + factoryAddress: string, + salt: string, + proxyBytecode: string, + constructorArgs: string, +): Promise { + const [deployer] = await hre.ethers.getSigners(); + const initCode = hre.ethers.concat([proxyBytecode, constructorArgs]); + const expectedAddress = hre.ethers.getCreate2Address(factoryAddress, salt, hre.ethers.keccak256(initCode)); + const factoryContract = await hre.ethers.getContractAt( + [ + 'function deploy(bytes32 salt, bytes memory initCode, bytes memory data, uint256 create2ForwardValue, uint256 callForwardValue) external payable returns (address deployed)', + 'event Deployed(address indexed addr)', + ], + factoryAddress, + deployer, + ); + const tx = await factoryContract.deploy(salt, initCode, '0x', 0, 0, { value: 0 }); + const receipt = await tx.wait(); + + if (!receipt) { + throw new Error('Deployment transaction failed'); + } + + let actualAddress = expectedAddress; + const deployedEvent = receipt.logs.find((log: any) => { + try { + const iface = new hre.ethers.Interface(['event Deployed(address indexed addr)']); + const parsed = iface.parseLog(log); + return parsed?.name === 'Deployed'; + } catch { + return false; + } + }); + + if (deployedEvent) { + const iface = new hre.ethers.Interface(['event Deployed(address indexed addr)']); + const parsed = iface.parseLog(deployedEvent); + actualAddress = parsed?.args[0]; + } + + if (actualAddress.toLowerCase() !== expectedAddress.toLowerCase()) { + console.warn(`Warning: Actual address ${actualAddress} does not match expected address ${expectedAddress}`); + } + + return { + address: actualAddress, + txHash: receipt.hash, + deployTransaction: tx, + remoteDeploymentId: '', + }; +} + +export async function deployViaMulticall( + hre: HardhatRuntimeEnvironment, + multicallAddress: string, + factoryAddress: string, + salt: string, + proxyBytecode: string, + constructorArgs: string, + expectedProxyAddress: string, + initData: string, + deployerAddress?: string, + shouldTransferOwnership = false, + postDeployCalls: Array<{ target?: string; data: string; value?: string }> = [], +): Promise { + const [deployer] = await hre.ethers.getSigners(); + const initCode = hre.ethers.concat([proxyBytecode, constructorArgs]); + const factoryInterface = new hre.ethers.Interface([ + 'function deploy(bytes32 salt, bytes memory initCode, bytes memory data, uint256 create2ForwardValue, uint256 callForwardValue) external payable returns (address deployed)', + ]); + const deployCallData = factoryInterface.encodeFunctionData('deploy', [salt, initCode, '0x', 0, 0]); + const multicall = await hre.ethers.getContractAt( + [ + 'function aggregate3(tuple(address target, bool allowFailure, bytes callData)[] calldata calls) external payable returns (tuple(bool success, bytes returnData)[] memory returnData)', + ], + multicallAddress, + deployer, + ); + const calls = [ + { + target: factoryAddress, + allowFailure: false, + callData: deployCallData, + }, + ]; + + if (initData && initData !== '0x') { + calls.push({ + target: expectedProxyAddress, + allowFailure: false, + callData: initData, + }); + } + + for (const postDeployCall of postDeployCalls || []) { + if (postDeployCall.value && postDeployCall.value !== '0' && postDeployCall.value !== '0x0') { + throw new Error('create2Factory.postDeployCalls does not support non-zero value with aggregate3'); + } + calls.push({ + target: !postDeployCall.target || postDeployCall.target === '$proxy' ? expectedProxyAddress : postDeployCall.target, + allowFailure: false, + callData: postDeployCall.data, + }); + } + + if (shouldTransferOwnership && deployerAddress) { + const ownableInterface = new hre.ethers.Interface(['function transferOwnership(address newOwner)']); + calls.push({ + target: expectedProxyAddress, + allowFailure: false, + callData: ownableInterface.encodeFunctionData('transferOwnership', [deployerAddress]), + }); + } + + const tx = await multicall.aggregate3(calls, { value: 0 }); + const receipt = await tx.wait(); + + if (!receipt) { + throw new Error('Multicall deployment transaction failed'); + } + + return { + address: expectedProxyAddress, + txHash: receipt.hash, + deployTransaction: tx, + remoteDeploymentId: '', + }; +} diff --git a/node_modules/@openzeppelin/hardhat-upgrades/src/utils/options.ts b/node_modules/@openzeppelin/hardhat-upgrades/src/utils/options.ts index 3ee7954..f3e3718 100644 --- a/node_modules/@openzeppelin/hardhat-upgrades/src/utils/options.ts +++ b/node_modules/@openzeppelin/hardhat-upgrades/src/utils/options.ts @@ -22,6 +22,20 @@ export type DeployFactoryOpts = { * Allows to customize the deploy function used instead of utils/deploy.ts:deploy */ deployFunction?: () => Promise; + + /** + * Use a CREATE2 factory for proxy deployment. + */ + create2Factory?: { + address: string; + salt: string; + implementationAddress?: string; + implSalt?: string; + deployImpl?: boolean; + multicall?: string; + transferOwnership?: boolean; + postDeployCalls?: Array<{ target?: string; data: string; value?: string }>; + }; }; /**