-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcreate2factory.ts
158 lines (136 loc) · 5.97 KB
/
create2factory.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// Wrapper for https://github.com/Arachnid/deterministic-deployment-proxy
// Based on https://github.com/eth-infinitism/account-abstraction/blob/main/src/Create2Factory.ts
import { type Provider } from "@ethersproject/providers";
import { type Signer } from "@ethersproject/abstract-signer";
import { BigNumberish } from "@ethersproject/bignumber";
import { formatEther } from "@ethersproject/units";
import { hexConcat, hexlify, hexZeroPad, keccak256 } from 'ethers/lib/utils'
import { getArtifact } from "./artifact";
import {DeploymentSubmission} from "hardhat-deploy/dist/types";
import _ from "lodash";
// Analogous to hardhat-deploy:DeployOptions.
export interface Create2FactoryDeployOptions {
from: string;
initCode: string;
salt?: BigNumberish;
gasLimit?: BigNumberish;
abi?: any[]; // abi to store in deployments/. If not given, load from compiled artifact.
}
// Create2Factory
export class Create2Factory {
static readonly contractAddress = "0x4e59b44847b379578588920ca78fbf26c0b4956c";
static readonly deployerAddress = "0x3fab184622dc19b6109349b94811493bf2a45362";
static readonly deployRawTx = "0xf8a58085174876e800830186a08080b853604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf31ba02222222222222222222222222222222222222222222222222222222222222222a02222222222222222222222222222222222222222222222222222222222222222";
static readonly deployGasPrice = 100e9;
static readonly deployGasLimit = 100000;
static readonly deployFee = (Create2Factory.deployGasLimit * Create2Factory.deployGasPrice).toString(); // 0.01 ETH
private factoryExists = false;
private readonly provider: Provider;
constructor(provider: Provider) {
this.provider = provider;
}
// Deploy the factory itself.
async deployFactory(from_: string) {
if (await this._factoryExists()) {
console.log(`reusing "Create2Factory" at ${Create2Factory.contractAddress}`);
await this._saveDeployment("Create2Factory", {
abi: [],
address: Create2Factory.contractAddress,
});
return;
}
const signer = await hre.deployments.getSigner(from_);
// Top up fee to deployerAddress
const balance = await this.provider.getBalance(Create2Factory.deployerAddress);
if (balance.lt(Create2Factory.deployFee)) {
const feeTx = await signer.sendTransaction({
to: Create2Factory.deployerAddress,
value: Create2Factory.deployFee,
});
await feeTx.wait();
console.log(`topped up Create2Factory.deployerAddress with ${formatEther(Create2Factory.deployFee)} ETH (tx: ${feeTx.hash})`);
}
// Send hardcoded deploy tx
const deployTx = await this.provider.sendTransaction(Create2Factory.deployRawTx);
process.stdout.write(`deploying "Create2Factory" (tx: ${deployTx.hash})...: `);
const deployRc = await deployTx.wait();
console.log(`deployed at ${Create2Factory.contractAddress} with ${deployRc.gasUsed} gas`);
await this._saveDeployment("Create2Factory", {
abi: [], // no ABI
address: deployRc.contractAddress,
receipt: deployRc,
transactionHash: deployRc.transactionHash,
deployedBytecode: await this.provider.getCode(Create2Factory.contractAddress),
});
}
// Deploy a contract using the factory. Use it when you want to use a hardcoded initCode.
// If you can use compiled artifact, use hre.deployments.deploy(name, { deterministicDeployment: true });
async deployContract(name: string, options: Create2FactoryDeployOptions) {
if (!(await this._factoryExists())) {
throw new Error("Create2Factory not deployed");
}
const signer = await hre.deployments.getSigner(options.from);
const initCode = options.initCode;
const salt = options.salt ?? 0;
const gasLimit = options.gasLimit;
const abi = options.abi ?? (await getArtifact(name)).abi;
const contractAddress = Create2Factory.getDeployedAddress(initCode, salt);
const calldata = Create2Factory.getDeployCalldata(initCode, salt);
const code = await this.provider.getCode(contractAddress);
if (code.length > 2) {
console.log(`reusing "${name}" at ${contractAddress}`);
await this._saveDeployment(name, {
abi: abi,
address: contractAddress,
deployedBytecode: code,
});
return;
}
const deployTx = await signer.sendTransaction({
to: Create2Factory.contractAddress,
data: calldata,
gasLimit: gasLimit,
});
process.stdout.write(`deploying "${name}" (tx: ${deployTx.hash})...: `);
const deployRc = await deployTx.wait();
// The deployment is an internal transaction, so the deployRc.contractAddress is null.
console.log(`deployed at ${contractAddress} with ${deployRc.gasUsed} gas`);
await this._saveDeployment(name, {
abi: abi,
address: contractAddress,
receipt: deployRc,
transactionHash: deployRc.transactionHash,
deployedBytecode: await this.provider.getCode(contractAddress),
});
}
async _factoryExists(): Promise<boolean> {
if (!this.factoryExists) {
const code = await this.provider.getCode(Create2Factory.contractAddress);
this.factoryExists = (code.length > 2);
}
return this.factoryExists;
}
async _saveDeployment(name: string, deployment: DeploymentSubmission) {
const previous = await hre.deployments.getOrNull(name);
if (previous != null) {
return; // Do not overwrite existing deployment.
}
await hre.deployments.save(name, deployment);
}
static getDeployCalldata(initCode: string, salt: BigNumberish): string {
const saltBytes32 = hexZeroPad(hexlify(salt), 32)
return hexConcat([
saltBytes32,
initCode
])
}
static getDeployedAddress(initCode: string, salt: BigNumberish): string {
const saltBytes32 = hexZeroPad(hexlify(salt), 32)
return '0x' + keccak256(hexConcat([
'0xff',
Create2Factory.contractAddress,
saltBytes32,
keccak256(initCode)
])).slice(-40);
}
};