diff --git a/src/factory/deployModuleFactory.ts b/src/factory/deployModuleFactory.ts index 0d119095..4bcef491 100644 --- a/src/factory/deployModuleFactory.ts +++ b/src/factory/deployModuleFactory.ts @@ -1,40 +1,44 @@ import "hardhat-deploy"; import "@nomiclabs/hardhat-ethers"; +import { constants as ethersConstants } from "ethers"; import { HardhatRuntimeEnvironment } from "hardhat/types"; import { getSingletonFactory } from "./singletonFactory"; -const factorySalt = +const { AddressZero } = ethersConstants; + +const FactorySalt = "0xb0519c4c4b7945db302f69180b86f1a668153a476802c1c445fcb691ef23ef16"; -const AddressZero = "0x0000000000000000000000000000000000000000"; /** * Deploy a module factory via the singleton factory. * It will therefore get the same address on any chain. + * * @param hre hardhat runtime environment - * @returns The address of the deployed module factory + * @returns The address of the deployed module factory, or the zero address if it was already deployed */ -export const deployModuleFactory = async (hre: HardhatRuntimeEnvironment) => { +export const deployModuleFactory = async ( + hre: HardhatRuntimeEnvironment +): Promise => { const singletonFactory = await getSingletonFactory(hre); console.log(" Singleton Factory: ", singletonFactory.address); const Factory = await hre.ethers.getContractFactory("ModuleProxyFactory"); - // const singletonFactory = new hardhat.ethers.Contract(singletonFactoryAddress, singletonFactoryAbi) const targetAddress = await singletonFactory.callStatic.deploy( Factory.bytecode, - factorySalt + FactorySalt ); - if (targetAddress == AddressZero) { + if (targetAddress === AddressZero) { console.log( " ModuleProxyFactory already deployed to target address on this network." ); - return; - } else { - console.log(" Target Factory Address:", targetAddress); + return AddressZero; } + console.log(" Target Factory Address:", targetAddress); + const transactionResponse = await singletonFactory.deploy( Factory.bytecode, - factorySalt, + FactorySalt, { gasLimit: 1000000 } ); @@ -51,7 +55,7 @@ export const deployModuleFactory = async (hre: HardhatRuntimeEnvironment) => { ); if ( - (await hre.ethers.provider.getCode(factory.address)) != + (await hre.ethers.provider.getCode(factory.address)) !== factoryArtifact.deployedBytecode ) { throw new Error( diff --git a/src/factory/mastercopyDeployer.ts b/src/factory/mastercopyDeployer.ts index 782db110..72d0b716 100644 --- a/src/factory/mastercopyDeployer.ts +++ b/src/factory/mastercopyDeployer.ts @@ -1,57 +1,123 @@ -import { BytesLike, ContractFactory } from "ethers"; +import { + BytesLike, + ContractFactory, + constants as ethersConstants, +} from "ethers"; +import { keccak256, getCreate2Address, getAddress } from "ethers/lib/utils"; import { HardhatRuntimeEnvironment } from "hardhat/types"; +import assert from "node:assert"; import { getSingletonFactory } from "./singletonFactory"; +const { AddressZero } = ethersConstants; + /** * Deploy a module's mastercopy via the singleton factory. * * To get the same address on any chain. * @param hre hardhat runtime environment - * @param mastercopyContractFactory - * @param args - * @returns The address of the deployed module mastercopy + * @param mastercopyContractFactory mastercopy to deploy + * @param args the arguments to pass to the mastercopy's constructor + * @returns The address of the deployed module mastercopy or the zero address if it was already deployed */ export const deployMastercopy = async ( hre: HardhatRuntimeEnvironment, mastercopyContractFactory: ContractFactory, args: Array, salt: string -) => { +): Promise => { const deploymentTx = mastercopyContractFactory.getDeployTransaction(...args); - if (deploymentTx.data) { - await deployMastercopyWithInitData(hre, deploymentTx.data, salt); + + if (Array.isArray(deploymentTx.data) && deploymentTx.data.length > 0) { + return await deployMastercopyWithInitData(hre, deploymentTx.data, salt); } + throw new Error("Unable to create the deployment data (no init code)."); +}; + +/** + * Compute a module's mastercopy address. Where it is or will be deployed. And checks if it is already deployed. + * + * @param hre hardhat runtime environment + * @param mastercopyContractFactory mastercopy to get address for + * @param args the arguments passed to the mastercopy's constructor + * @returns { + * address: string; // the address where the module mastercopy will be deployed or was already deployed + * isDeployed: boolean; // true if the module mastercopy was already deployed on this chain + * } + */ +export const computeTargetAddress = async ( + hre: HardhatRuntimeEnvironment, + mastercopyContractFactory: ContractFactory, + args: Array, + salt: string +): Promise<{ address: string; isDeployed: boolean }> => { + const deploymentTx = mastercopyContractFactory.getDeployTransaction(...args); + const singletonFactory = await getSingletonFactory(hre); + + if (!Array.isArray(deploymentTx.data) || deploymentTx.data.length === 0) { + throw new Error("Unable to create the deployment data (no init code)."); + } + + const initCodeHash = keccak256(deploymentTx.data); + + const computedAddress = getCreate2Address( + singletonFactory.address, + salt, + initCodeHash + ); + + const targetAddress = getAddress( + (await singletonFactory.callStatic.deploy( + deploymentTx.data, + salt + )) as string + ); + + // Sanity check + assert( + computedAddress === targetAddress || targetAddress === AddressZero, + "The computed address does not match the target address and the target address is not 0x0." + ); + + return { + address: computedAddress, + isDeployed: targetAddress === AddressZero, + }; }; export const deployMastercopyWithInitData = async ( hre: HardhatRuntimeEnvironment, initCode: BytesLike, salt: string -) => { +): Promise => { const singletonFactory = await getSingletonFactory(hre); - const targetAddress = await singletonFactory.callStatic.deploy( - initCode, - salt + // throws if this for some reason is not a valid address + const targetAddress = getAddress( + (await singletonFactory.callStatic.deploy(initCode, salt)) as string ); - const initCodeHash = await hre.ethers.utils.solidityKeccak256( - ["bytes"], - [initCode] - ); - const computedTargetAddress = await hre.ethers.utils.getCreate2Address( + const initCodeHash = keccak256(initCode); + + const computedTargetAddress = getCreate2Address( singletonFactory.address, salt, initCodeHash ); - if (targetAddress == "0x0000000000000000000000000000000000000000") { + if (targetAddress === AddressZero) { console.log( ` ✔ Mastercopy already deployed to: ${computedTargetAddress}` ); - return; + return AddressZero; } + // Sanity check + assert.equal( + targetAddress, + computedTargetAddress, + "The computed address does not match the target address." + ); + let deployData; switch (hre.network.name) { case "optimism": diff --git a/src/factory/singletonFactory.ts b/src/factory/singletonFactory.ts index 88e3a3e9..208b3b12 100644 --- a/src/factory/singletonFactory.ts +++ b/src/factory/singletonFactory.ts @@ -3,7 +3,7 @@ import { HardhatRuntimeEnvironment } from "hardhat/types"; const singletonFactoryAbi = [ "function deploy(bytes memory _initCode, bytes32 _salt) public returns (address payable createdContract)", ]; -const singletonFactoryAddress = "0xce0042b868300000d44a59004da54a005ffdcf9f"; +const SingletonFactoryAddress = "0xce0042b868300000d44a59004da54a005ffdcf9f"; /** * Get the singleton factory contract (ERC-2470). @@ -20,7 +20,7 @@ export const getSingletonFactory = async ( const singletonDeployer = "0xBb6e024b9cFFACB947A71991E386681B1Cd1477D"; const singletonFactory = new hardhat.ethers.Contract( - singletonFactoryAddress, + SingletonFactoryAddress, singletonFactoryAbi, deployer );