diff --git a/node_modules/@openzeppelin/hardhat-upgrades/dist/deploy-proxy.js b/node_modules/@openzeppelin/hardhat-upgrades/dist/deploy-proxy.js index 01e2cd9..04e8e48 100644 --- a/node_modules/@openzeppelin/hardhat-upgrades/dist/deploy-proxy.js +++ b/node_modules/@openzeppelin/hardhat-upgrades/dist/deploy-proxy.js @@ -15,7 +15,95 @@ 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; + + // Check if deployment of implementation contract should use CREATE2 + if (opts.create2Factory && opts.create2Factory.deployImpl !== false) { + const { getDeployData } = await Promise.resolve().then(() => require('./utils/deploy-impl')); + + // Get deployment data for validation + 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; + + // Get the implementation contract bytecode + const implBytecode = ImplFactory.bytecode; + + // Implementation contract salt + let implSalt = opts.create2Factory.implSalt; + if (!implSalt) { + // If implSalt is not provided, generate one based on proxySalt + const proxySaltBytes = hre.ethers.getBytes(opts.create2Factory.salt); + const implSaltBytes = new Uint8Array(proxySaltBytes); + implSaltBytes[implSaltBytes.length - 1] ^= 0x01; + implSalt = hre.ethers.hexlify(implSaltBytes); + } + + // Calculate expected address to check if already deployed + const initCode = hre.ethers.concat([implBytecode, '0x']); + const expectedAddress = hre.ethers.getCreate2Address( + opts.create2Factory.address, + implSalt, + hre.ethers.keccak256(initCode) + ); + + // Check if implementation already exists on chain + const existingCode = await hre.ethers.provider.getCode(expectedAddress); + const implExists = existingCode !== '0x'; + + // Use fetchOrDeployGetDeployment to deploy and record to manifest + const deployment = await upgrades_core_1.fetchOrDeployGetDeployment( + deployData.version, + deployData.provider, + async () => { + const abi = ImplFactory.interface.format(true); + + if (implExists) { + // Contract already exists, just return the info + console.log(` Contract already exists at address: ${expectedAddress}`); + return { + abi, + layout: deployData.layout, + address: expectedAddress, + txHash: undefined, + }; + } + + // Deploy implementation contract via CREATE2 + const { deployViaCreate2Factory } = await Promise.resolve().then(() => require('./utils/deploy')); + 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 (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; @@ -44,7 +132,54 @@ 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)); + + // Check if using CREATE2 Factory + if (opts.create2Factory) { + // Get the proxy contract bytecode + const proxyBytecode = ProxyFactory.bytecode; + + // Encode constructor arguments: constructor(address implementation, bytes memory _data) + const constructorArgs = hre.ethers.AbiCoder.defaultAbiCoder().encode( + ['address', 'bytes'], + [impl, data] + ); + + // Calculate expected proxy address + const proxyInitCode = hre.ethers.concat([proxyBytecode, constructorArgs]); + const expectedProxyAddress = hre.ethers.getCreate2Address( + opts.create2Factory.address, + opts.create2Factory.salt, + hre.ethers.keccak256(proxyInitCode) + ); + + // Check if proxy already exists + const existingProxyCode = await hre.ethers.provider.getCode(expectedProxyAddress); + const proxyExists = existingProxyCode !== '0x'; + + if (proxyExists) { + console.log(` Contract already exists at address: ${expectedProxyAddress}`); + proxyDeployment = { + kind, + address: expectedProxyAddress, + txHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + remoteDeploymentId: '' + }; + } else { + // Deploy using CREATE2 Factory + const { deployViaCreate2Factory } = await Promise.resolve().then(() => require('./utils/deploy')); + const deployment = await deployViaCreate2Factory( + hre, + opts.create2Factory.address, + opts.create2Factory.salt, + proxyBytecode, + constructorArgs + ); + proxyDeployment = Object.assign({ kind }, deployment); + } + } else { + // Original deployment logic + proxyDeployment = Object.assign({ kind }, await deployFn(hre, opts, ProxyFactory, impl, data)); + } break; } case 'transparent': { @@ -53,7 +188,54 @@ 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)); + + // Check if using CREATE2 Factory + if (opts.create2Factory) { + // Get the proxy contract bytecode + const proxyBytecode = TransparentUpgradeableProxyFactory.bytecode; + + // Encode constructor arguments: constructor(address _logic, address initialOwner, bytes memory _data) + const constructorArgs = hre.ethers.AbiCoder.defaultAbiCoder().encode( + ['address', 'address', 'bytes'], + [impl, initialOwner, data] + ); + + // Calculate expected proxy address + const proxyInitCode = hre.ethers.concat([proxyBytecode, constructorArgs]); + const expectedProxyAddress = hre.ethers.getCreate2Address( + opts.create2Factory.address, + opts.create2Factory.salt, + hre.ethers.keccak256(proxyInitCode) + ); + + // Check if proxy already exists + const existingProxyCode = await hre.ethers.provider.getCode(expectedProxyAddress); + const proxyExists = existingProxyCode !== '0x'; + + if (proxyExists) { + console.log(` Contract already exists at address: ${expectedProxyAddress}`); + proxyDeployment = { + kind, + address: expectedProxyAddress, + txHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + remoteDeploymentId: '' + }; + } else { + // Deploy using CREATE2 Factory + const { deployViaCreate2Factory } = await Promise.resolve().then(() => require('./utils/deploy')); + const deployment = await deployViaCreate2Factory( + hre, + opts.create2Factory.address, + opts.create2Factory.salt, + proxyBytecode, + constructorArgs + ); + proxyDeployment = Object.assign({ kind }, deployment); + } + } else { + // Original deployment logic + proxyDeployment = Object.assign({ kind }, await deployFn(hre, opts, TransparentUpgradeableProxyFactory, impl, initialOwner, data)); + } break; } } diff --git a/node_modules/@openzeppelin/hardhat-upgrades/dist/utils/deploy.js b/node_modules/@openzeppelin/hardhat-upgrades/dist/utils/deploy.js index 46c2c7f..7204eb1 100644 --- a/node_modules/@openzeppelin/hardhat-upgrades/dist/utils/deploy.js +++ b/node_modules/@openzeppelin/hardhat-upgrades/dist/utils/deploy.js @@ -1,6 +1,7 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.deploy = deploy; +exports.deployViaCreate2Factory = deployViaCreate2Factory; const deploy_1 = require("../defender/deploy"); async function deploy(hre, opts, factory, ...args) { if (opts?.useDefenderDeploy) { @@ -23,4 +24,76 @@ async function ethersDeploy(factory, ...args) { const txHash = deployTransaction.hash; return { address, txHash, deployTransaction }; } + +/** + * Deploy contract using CREATE2 Factory + */ +async function deployViaCreate2Factory(hre, factoryAddress, salt, proxyBytecode, constructorArgs) { + const [deployer] = await hre.ethers.getSigners(); + + // Assemble the complete initCode + const initCode = hre.ethers.concat([proxyBytecode, constructorArgs]); + + // Calculate the expected address + const expectedAddress = hre.ethers.getCreate2Address( + factoryAddress, + salt, + hre.ethers.keccak256(initCode) + ); + + // Get the Factory contract instance + 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 + ); + + // Call the deploy function + 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'); + } + + // Get the actual deployed address from the event + 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: '', + }; +} //# sourceMappingURL=deploy.js.map \ No newline at end of file diff --git a/node_modules/@openzeppelin/hardhat-upgrades/src/deploy-proxy.ts b/node_modules/@openzeppelin/hardhat-upgrades/src/deploy-proxy.ts index b912102..fa1f37a 100644 --- a/node_modules/@openzeppelin/hardhat-upgrades/src/deploy-proxy.ts +++ b/node_modules/@openzeppelin/hardhat-upgrades/src/deploy-proxy.ts @@ -52,7 +52,96 @@ 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: ProxyDeployment['kind']; + + // Check if deploying implementation contract using CREATE2 + if (opts.create2Factory && opts.create2Factory.deployImpl !== false) { + const { getDeployData } = await import('./utils/deploy-impl'); + const { fetchOrDeployGetDeployment } = await import('@openzeppelin/upgrades-core'); + + // Get deployment data for validation + const deployData = await getDeployData(hre, ImplFactory, opts); + const { validateProxyImpl } = await import('./utils/validate-impl'); + await validateProxyImpl(deployData, opts); + + if (opts.kind === undefined) { + throw new Error('Broken invariant: Proxy kind is undefined'); + } + kind = opts.kind; + + // Get the implementation contract bytecode + const implBytecode = ImplFactory.bytecode; + + // Implementation contract salt (if provided, use it; otherwise use modified salt) + let implSalt = opts.create2Factory.implSalt; + if (!implSalt) { + // If implSalt is not provided, generate one based on proxySalt (flip the last byte) + const proxySaltBytes = hre.ethers.getBytes(opts.create2Factory.salt); + const implSaltBytes = new Uint8Array(proxySaltBytes); + implSaltBytes[implSaltBytes.length - 1] ^= 0x01; // Flip the last byte + implSalt = hre.ethers.hexlify(implSaltBytes); + } + + // Calculate expected address to check if already deployed + const initCode = hre.ethers.concat([implBytecode, '0x']); + const expectedAddress = hre.ethers.getCreate2Address( + opts.create2Factory.address, + implSalt, + hre.ethers.keccak256(initCode) + ); + + // Check if implementation already exists on chain + const existingCode = await hre.ethers.provider.getCode(expectedAddress); + const implExists = existingCode !== '0x'; + + // Use fetchOrDeployGetDeployment to deploy and record to manifest + const deployment = await fetchOrDeployGetDeployment( + deployData.version, + deployData.provider, + async () => { + const abi = ImplFactory.interface.format(true) as string[]; + + if (implExists) { + // Contract already exists, just return the info + console.log(` Contract already exists at address: ${expectedAddress}`); + return { + abi, + layout: deployData.layout, + address: expectedAddress, + txHash: undefined as any, + }; + } + + // Deploy implementation contract via CREATE2 + const { deployViaCreate2Factory } = await import('./utils/deploy'); + const implDeployment = await deployViaCreate2Factory( + hre, + opts.create2Factory.address, + implSalt, + implBytecode, + '0x' // Implementation contract usually has no constructor arguments + ); + return { + abi, + layout: deployData.layout, + address: implDeployment.address, + txHash: implDeployment.txHash, + deployTransaction: implDeployment.deployTransaction, + remoteDeploymentId: implDeployment.remoteDeploymentId || '' + }; + }, + opts, + false + ); + + impl = deployment.address; + } else { + // Original deployment logic + const result = await deployProxyImpl(hre, ImplFactory, opts); + impl = result.impl; + kind = result.kind; + } const contractInterface = ImplFactory.interface; const data = getInitializerData(contractInterface, args, opts.initializer); @@ -86,7 +175,54 @@ 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)); + + // Check if using CREATE2 Factory + if (opts.create2Factory) { + // Get the proxy contract bytecode + const proxyBytecode = ProxyFactory.bytecode; + + // Encode constructor arguments: constructor(address implementation, bytes memory _data) + const constructorArgs = hre.ethers.AbiCoder.defaultAbiCoder().encode( + ['address', 'bytes'], + [impl, data] + ); + + // Calculate expected proxy address + const proxyInitCode = hre.ethers.concat([proxyBytecode, constructorArgs]); + const expectedProxyAddress = hre.ethers.getCreate2Address( + opts.create2Factory.address, + opts.create2Factory.salt, + hre.ethers.keccak256(proxyInitCode) + ); + + // Check if proxy already exists + const existingProxyCode = await hre.ethers.provider.getCode(expectedProxyAddress); + const proxyExists = existingProxyCode !== '0x'; + + if (proxyExists) { + console.log(` Contract already exists at address: ${expectedProxyAddress}`); + proxyDeployment = { + kind, + address: expectedProxyAddress, + txHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + remoteDeploymentId: '' + }; + } else { + // Deploy using CREATE2 Factory + const { deployViaCreate2Factory } = await import('./utils/deploy'); + const deployment = await deployViaCreate2Factory( + hre, + opts.create2Factory.address, + opts.create2Factory.salt, + proxyBytecode, + constructorArgs + ); + proxyDeployment = Object.assign({ kind }, deployment); + } + } else { + // Original deployment logic + proxyDeployment = Object.assign({ kind }, await deployFn(hre, opts, ProxyFactory, impl, data)); + } break; } @@ -103,10 +239,57 @@ 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), - ); + + // Check if using CREATE2 Factory + if (opts.create2Factory) { + // Get the proxy contract bytecode + const proxyBytecode = TransparentUpgradeableProxyFactory.bytecode; + + // Encode constructor arguments: constructor(address _logic, address initialOwner, bytes memory _data) + const constructorArgs = hre.ethers.AbiCoder.defaultAbiCoder().encode( + ['address', 'address', 'bytes'], + [impl, initialOwner, data] + ); + + // Calculate expected proxy address + const proxyInitCode = hre.ethers.concat([proxyBytecode, constructorArgs]); + const expectedProxyAddress = hre.ethers.getCreate2Address( + opts.create2Factory.address, + opts.create2Factory.salt, + hre.ethers.keccak256(proxyInitCode) + ); + + // Check if proxy already exists + const existingProxyCode = await hre.ethers.provider.getCode(expectedProxyAddress); + const proxyExists = existingProxyCode !== '0x'; + + if (proxyExists) { + console.log(` Contract already exists at address: ${expectedProxyAddress}`); + proxyDeployment = { + kind, + address: expectedProxyAddress, + txHash: '0x0000000000000000000000000000000000000000000000000000000000000000', + remoteDeploymentId: '' + }; + } else { + // Deploy using CREATE2 Factory + const { deployViaCreate2Factory } = await import('./utils/deploy'); + const deployment = await deployViaCreate2Factory( + hre, + opts.create2Factory.address, + opts.create2Factory.salt, + proxyBytecode, + constructorArgs + ); + proxyDeployment = Object.assign({ kind }, deployment); + } + } else { + // Original deployment logic + 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..a8e2432 100644 --- a/node_modules/@openzeppelin/hardhat-upgrades/src/utils/deploy.ts +++ b/node_modules/@openzeppelin/hardhat-upgrades/src/utils/deploy.ts @@ -45,3 +45,77 @@ 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(); + + // Assemble the complete initCode = bytecode + constructor args + const initCode = hre.ethers.concat([proxyBytecode, constructorArgs]); + + // Calculate the expected address + const expectedAddress = hre.ethers.getCreate2Address( + factoryAddress, + salt, + hre.ethers.keccak256(initCode) + ); + + // Get the Factory contract instance + 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 + ); + + // Call the deploy function + const tx = await factoryContract.deploy( + salt, + initCode, + '0x', // After deployment, no additional call is needed + 0, // create2ForwardValue + 0, // callForwardValue + { value: 0 } + ); + const receipt = await tx.wait(); + + if (!receipt) { + throw new Error('Deployment transaction failed'); + } + + // Get the actual deployed address from the event + 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: '', + }; +} diff --git a/node_modules/@openzeppelin/hardhat-upgrades/src/utils/options.ts b/node_modules/@openzeppelin/hardhat-upgrades/src/utils/options.ts index 3ee7954..05b4684 100644 --- a/node_modules/@openzeppelin/hardhat-upgrades/src/utils/options.ts +++ b/node_modules/@openzeppelin/hardhat-upgrades/src/utils/options.ts @@ -22,6 +22,16 @@ export type DeployFactoryOpts = { * Allows to customize the deploy function used instead of utils/deploy.ts:deploy */ deployFunction?: () => Promise; + + /** + * Use CREATE2 Factory to deploy proxy and implementation contracts, ensuring consistent addresses across chains + */ + create2Factory?: { + address: string; // Create2 Factory contract address + salt: string; // 32 bytes salt for proxy contract + implSalt?: string; // 32 bytes salt for implementation contract (optional) + deployImpl?: boolean; // Whether to deploy implementation contract using CREATE2 (default true) + }; }; /**