From 732dd42eae5434512c73f7049e50852d3d3cc39d Mon Sep 17 00:00:00 2001 From: Asgeir Date: Wed, 21 Dec 2022 16:09:58 +0100 Subject: [PATCH 1/3] Return the address when deploying a mastercopy Also, add a `computeTargetAddress` function that can be used to get the address of an already deployed mastercopy --- src/factory/mastercopyDeployer.ts | 90 ++++++++++++++++++++++++++----- 1 file changed, 77 insertions(+), 13 deletions(-) diff --git a/src/factory/mastercopyDeployer.ts b/src/factory/mastercopyDeployer.ts index 782db110..1290ff02 100644 --- a/src/factory/mastercopyDeployer.ts +++ b/src/factory/mastercopyDeployer.ts @@ -1,4 +1,4 @@ -import { BytesLike, ContractFactory } from "ethers"; +import { BytesLike, ContractFactory, utils } from "ethers"; import { HardhatRuntimeEnvironment } from "hardhat/types"; import { getSingletonFactory } from "./singletonFactory"; @@ -7,51 +7,115 @@ import { getSingletonFactory } from "./singletonFactory"; * * 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 undefined 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); + return await deployMastercopyWithInitData(hre, deploymentTx.data, salt); + } + throw new Error("No deployment data found"); +}; + +/** + * Compute a module's mastercopy address. Where it is or will be 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 (!deploymentTx.data) { + throw new Error("No deployment data found"); } + + const initCodeHash = hre.ethers.utils.solidityKeccak256( + ["bytes"], + [deploymentTx.data] + ); + const computedAddress = hre.ethers.utils.getCreate2Address( + singletonFactory.address, + salt, + initCodeHash + ); + + const targetAddress = utils.getAddress( + (await singletonFactory.callStatic.deploy( + deploymentTx.data, + salt + )) as string + ); + + // Sanity check + if ( + computedAddress !== targetAddress && + targetAddress !== "0x0000000000000000000000000000000000000000" + ) { + throw new Error( + "The computed address does not match the target address and the target address is not 0x0." + ); + } + + return { + address: computedAddress, + isDeployed: targetAddress === "0x0000000000000000000000000000000000000000", + }; }; 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 = utils.getAddress( + (await singletonFactory.callStatic.deploy(initCode, salt)) as string ); - const initCodeHash = await hre.ethers.utils.solidityKeccak256( + const initCodeHash = hre.ethers.utils.solidityKeccak256( ["bytes"], [initCode] ); - const computedTargetAddress = await hre.ethers.utils.getCreate2Address( + const computedTargetAddress = hre.ethers.utils.getCreate2Address( singletonFactory.address, salt, initCodeHash ); - if (targetAddress == "0x0000000000000000000000000000000000000000") { + if (targetAddress === "0x0000000000000000000000000000000000000000") { console.log( ` ✔ Mastercopy already deployed to: ${computedTargetAddress}` ); return; } + // Sanity check + if (computedTargetAddress !== targetAddress) { + throw new Error("The computed address does not match the target address."); + } + let deployData; switch (hre.network.name) { case "optimism": From 757a16f65fa2317fc9014409b552b682bd799068 Mon Sep 17 00:00:00 2001 From: Asgeir Date: Thu, 22 Dec 2022 09:58:11 +0100 Subject: [PATCH 2/3] Improving code quality of mastercopyDeployer Incorporating review comments from @cristovaoth ++ --- src/factory/mastercopyDeployer.ts | 72 ++++++++++++++++--------------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/src/factory/mastercopyDeployer.ts b/src/factory/mastercopyDeployer.ts index 1290ff02..72d0b716 100644 --- a/src/factory/mastercopyDeployer.ts +++ b/src/factory/mastercopyDeployer.ts @@ -1,7 +1,15 @@ -import { BytesLike, ContractFactory, utils } 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. * @@ -9,24 +17,24 @@ import { getSingletonFactory } from "./singletonFactory"; * @param hre hardhat runtime environment * @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 undefined if it was already deployed + * @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 => { +): Promise => { const deploymentTx = mastercopyContractFactory.getDeployTransaction(...args); - if (deploymentTx.data) { + if (Array.isArray(deploymentTx.data) && deploymentTx.data.length > 0) { return await deployMastercopyWithInitData(hre, deploymentTx.data, salt); } - throw new Error("No deployment data found"); + 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. + * 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 @@ -45,21 +53,19 @@ export const computeTargetAddress = async ( const deploymentTx = mastercopyContractFactory.getDeployTransaction(...args); const singletonFactory = await getSingletonFactory(hre); - if (!deploymentTx.data) { - throw new Error("No deployment data found"); + if (!Array.isArray(deploymentTx.data) || deploymentTx.data.length === 0) { + throw new Error("Unable to create the deployment data (no init code)."); } - const initCodeHash = hre.ethers.utils.solidityKeccak256( - ["bytes"], - [deploymentTx.data] - ); - const computedAddress = hre.ethers.utils.getCreate2Address( + const initCodeHash = keccak256(deploymentTx.data); + + const computedAddress = getCreate2Address( singletonFactory.address, salt, initCodeHash ); - const targetAddress = utils.getAddress( + const targetAddress = getAddress( (await singletonFactory.callStatic.deploy( deploymentTx.data, salt @@ -67,18 +73,14 @@ export const computeTargetAddress = async ( ); // Sanity check - if ( - computedAddress !== targetAddress && - targetAddress !== "0x0000000000000000000000000000000000000000" - ) { - throw new Error( - "The computed address does not match the target address and the target address is not 0x0." - ); - } + 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 === "0x0000000000000000000000000000000000000000", + isDeployed: targetAddress === AddressZero, }; }; @@ -86,35 +88,35 @@ export const deployMastercopyWithInitData = async ( hre: HardhatRuntimeEnvironment, initCode: BytesLike, salt: string -): Promise => { +): Promise => { const singletonFactory = await getSingletonFactory(hre); // throws if this for some reason is not a valid address - const targetAddress = utils.getAddress( + const targetAddress = getAddress( (await singletonFactory.callStatic.deploy(initCode, salt)) as string ); - const initCodeHash = hre.ethers.utils.solidityKeccak256( - ["bytes"], - [initCode] - ); - const computedTargetAddress = 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 - if (computedTargetAddress !== targetAddress) { - throw new Error("The computed address does not match the target address."); - } + assert.equal( + targetAddress, + computedTargetAddress, + "The computed address does not match the target address." + ); let deployData; switch (hre.network.name) { From 3ab13ad82f338006d3627545e219f13fd86bba1a Mon Sep 17 00:00:00 2001 From: Asgeir Date: Thu, 22 Dec 2022 09:59:28 +0100 Subject: [PATCH 3/3] Apply improvements to the rest of the deployment functionality for consistency --- src/factory/deployModuleFactory.ts | 28 ++++++++++++++++------------ src/factory/singletonFactory.ts | 4 ++-- 2 files changed, 18 insertions(+), 14 deletions(-) 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/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 );