Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return the address when deploying a mastercopy #98

Merged
merged 3 commits into from
Dec 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 16 additions & 12 deletions src/factory/deployModuleFactory.ts
Original file line number Diff line number Diff line change
@@ -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<string> => {
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 }
);

Expand All @@ -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(
Expand Down
102 changes: 84 additions & 18 deletions src/factory/mastercopyDeployer.ts
Original file line number Diff line number Diff line change
@@ -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<any>,
salt: string
) => {
): Promise<string> => {
const deploymentTx = mastercopyContractFactory.getDeployTransaction(...args);
asgeir-s marked this conversation as resolved.
Show resolved Hide resolved
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).");
};
asgeir-s marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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,
asgeir-s marked this conversation as resolved.
Show resolved Hide resolved
mastercopyContractFactory: ContractFactory,
args: Array<any>,
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,
};
asgeir-s marked this conversation as resolved.
Show resolved Hide resolved
};

export const deployMastercopyWithInitData = async (
hre: HardhatRuntimeEnvironment,
initCode: BytesLike,
salt: string
) => {
): Promise<string> => {
const singletonFactory = await getSingletonFactory(hre);
asgeir-s marked this conversation as resolved.
Show resolved Hide resolved

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(
asgeir-s marked this conversation as resolved.
Show resolved Hide resolved
` ✔ Mastercopy already deployed to: ${computedTargetAddress}`
);
return;
return AddressZero;
}

// Sanity check
assert.equal(
targetAddress,
computedTargetAddress,
"The computed address does not match the target address."
);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not know this assert variation.

Usually I import from "assert"

And then simply do assert(a === b)

let deployData;
switch (hre.network.name) {
case "optimism":
Expand Down
4 changes: 2 additions & 2 deletions src/factory/singletonFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -20,7 +20,7 @@ export const getSingletonFactory = async (

const singletonDeployer = "0xBb6e024b9cFFACB947A71991E386681B1Cd1477D";
const singletonFactory = new hardhat.ethers.Contract(
singletonFactoryAddress,
SingletonFactoryAddress,
singletonFactoryAbi,
deployer
);
Expand Down