forked from Gravity-Bridge/Gravity-Bridge
-
Notifications
You must be signed in to change notification settings - Fork 2
/
contract-deployer.ts
366 lines (322 loc) · 12.9 KB
/
contract-deployer.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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
import { Gravity } from "./typechain/Gravity";
import { GravityERC721 } from "./typechain/GravityERC721";
import { TestERC20A } from "./typechain/TestERC20A";
import { TestERC20B } from "./typechain/TestERC20B";
import { TestERC20C } from "./typechain/TestERC20C";
import { TestERC721A } from "./typechain/TestERC721A";
import { ethers } from "ethers";
import fs from "fs";
import commandLineArgs from "command-line-args";
import axios, { AxiosError, AxiosRequestConfig, AxiosResponse } from "axios";
import { exit } from "process";
const args = commandLineArgs([
// the ethernum node used to deploy the contract
{ name: "eth-node", type: String },
// the cosmos node that will be used to grab the validator set via RPC (TODO),
{ name: "cosmos-node", type: String },
// the Ethereum private key that will contain the gas required to pay for the contact deployment
{ name: "eth-privkey", type: String },
// the gravity contract .json file
{ name: "contract", type: String },
// the gravityERC721 contract .json file
{ name: "contractERC721", type: String },
// test mode, if enabled this script deploys three ERC20 contracts for testing
{ name: "test-mode", type: String },
]);
// 4. Now, the deployer script hits a full node api, gets the Eth signatures of the valset from the latest block, and deploys the Ethereum contract.
// - We will consider the scenario that many deployers deploy many valid gravity eth contracts.
// 5. The deployer submits the address of the gravity contract that it deployed to Ethereum.
// - The gravity module checks the Ethereum chain for each submitted address, and makes sure that the gravity contract at that address is using the correct source code, and has the correct validator set.
type Validator = {
power: number;
ethereum_address: string;
};
type ValsetTypeWrapper = {
type: string;
value: Valset;
}
type Valset = {
members: Validator[];
nonce: number;
};
type ABCIWrapper = {
jsonrpc: string;
id: string;
result: ABCIResponse;
};
type ABCIResponse = {
response: ABCIResult
}
type ABCIResult = {
code: number
log: string,
info: string,
index: string,
value: string,
height: string,
codespace: string,
};
type StatusWrapper = {
jsonrpc: string,
id: string,
result: NodeStatus
};
type NodeInfo = {
protocol_version: JSON,
id: string,
listen_addr: string,
network: string,
version: string,
channels: string,
moniker: string,
other: JSON,
};
type SyncInfo = {
latest_block_hash: string,
latest_app_hash: string,
latest_block_height: Number
latest_block_time: string,
earliest_block_hash: string,
earliest_app_hash: string,
earliest_block_height: Number,
earliest_block_time: string,
catching_up: boolean,
}
type NodeStatus = {
node_info: NodeInfo,
sync_info: SyncInfo,
validator_info: JSON,
};
// sets the gas price for all contract deployments
const overrides = {
//gasPrice: 100000000000
}
async function deploy() {
var startTime = new Date();
const provider = await new ethers.providers.JsonRpcProvider(args["eth-node"]);
let wallet = new ethers.Wallet(args["eth-privkey"], provider);
if (args["test-mode"] == "True" || args["test-mode"] == "true") {
var success = false;
while (!success) {
var present = new Date();
var timeDiff: number = present.getTime() - startTime.getTime();
timeDiff = timeDiff / 1000
provider.getBlockNumber().then(_ => success = true).catch(_ => console.log("Ethereum RPC error, trying again"))
if (timeDiff > 600) {
console.log("Could not contact Ethereum RPC after 10 minutes, check the URL!")
exit(1)
}
await sleep(1000);
}
}
if (args["test-mode"] == "True" || args["test-mode"] == "true") {
console.log("Test mode, deploying ERC20 contracts");
// this handles several possible locations for the ERC20 artifacts
var erc20_a_path: string
var erc20_b_path: string
var erc20_c_path: string
var erc721_a_path: string
const main_location_a = "/gravity/solidity/artifacts/contracts/TestERC20A.sol/TestERC20A.json"
const main_location_b = "/gravity/solidity/artifacts/contracts/TestERC20B.sol/TestERC20B.json"
const main_location_c = "/gravity/solidity/artifacts/contracts/TestERC20C.sol/TestERC20C.json"
const main_location_721_a = "/gravity/solidity/artifacts/contracts/TestERC721A.sol/TestERC721A.json"
const alt_location_1_a = "/solidity/TestERC20A.json"
const alt_location_1_b = "/solidity/TestERC20B.json"
const alt_location_1_c = "/solidity/TestERC20C.json"
const alt_location_1_721a = "/solidity/TestERC721A.json"
const alt_location_2_a = "TestERC20A.json"
const alt_location_2_b = "TestERC20B.json"
const alt_location_2_c = "TestERC20C.json"
const alt_location_2_721a = "TestERC721A.json"
if (fs.existsSync(main_location_a)) {
erc20_a_path = main_location_a
erc20_b_path = main_location_b
erc20_c_path = main_location_c
erc721_a_path = main_location_721_a
} else if (fs.existsSync(alt_location_1_a)) {
erc20_a_path = alt_location_1_a
erc20_b_path = alt_location_1_b
erc20_c_path = alt_location_1_c
erc721_a_path = alt_location_1_721a
} else if (fs.existsSync(alt_location_2_a)) {
erc20_a_path = alt_location_2_a
erc20_b_path = alt_location_2_b
erc20_c_path = alt_location_2_c
erc721_a_path = alt_location_2_721a
} else {
console.log("Test mode was enabled but the ERC20 contracts can't be found!")
exit(1)
}
const { abi, bytecode } = getContractArtifacts(erc20_a_path);
const erc20Factory = new ethers.ContractFactory(abi, bytecode, wallet);
const testERC20 = (await erc20Factory.deploy(overrides)) as TestERC20A;
await testERC20.deployed();
const erc20TestAddress = testERC20.address;
console.log("ERC20 deployed at Address - ", erc20TestAddress);
const { abi: abi1, bytecode: bytecode1 } = getContractArtifacts(erc20_b_path);
const erc20Factory1 = new ethers.ContractFactory(abi1, bytecode1, wallet);
const testERC201 = (await erc20Factory1.deploy(overrides)) as TestERC20B;
await testERC201.deployed();
const erc20TestAddress1 = testERC201.address;
console.log("ERC20 deployed at Address - ", erc20TestAddress1);
const { abi: abi2, bytecode: bytecode2 } = getContractArtifacts(erc20_c_path);
const erc20Factory2 = new ethers.ContractFactory(abi2, bytecode2, wallet);
const testERC202 = (await erc20Factory2.deploy(overrides)) as TestERC20C;
await testERC202.deployed();
const erc20TestAddress2 = testERC202.address;
console.log("ERC20 deployed at Address - ", erc20TestAddress2);
const { abi: abi3, bytecode: bytecode3 } = getContractArtifacts(erc721_a_path);
const erc721Factory1 = new ethers.ContractFactory(abi3, bytecode3, wallet);
const testERC721 = (await erc721Factory1.deploy(overrides)) as TestERC721A;
await testERC721.deployed();
const erc721TestAddress = testERC721.address;
console.log("ERC721 deployed at Address - ", erc721TestAddress);
}
const gravityIdString = await getGravityId();
const gravityId = ethers.utils.formatBytes32String(gravityIdString);
console.log("Starting Gravity contract deploy");
const { abi, bytecode } = getContractArtifacts(args["contract"]);
const factory = new ethers.ContractFactory(abi, bytecode, wallet);
console.log("About to get latest Gravity valset");
const latestValset = await getLatestValset();
let eth_addresses = [];
let powers = [];
let powers_sum = 0;
// this MUST be sorted uniformly across all components of Gravity in this
// case we perform the sorting in module/x/gravity/keeper/types.go to the
// output of the endpoint should always be sorted correctly. If you're
// having strange problems with updating the validator set you should go
// look there.
for (let i = 0; i < latestValset.members.length; i++) {
if (latestValset.members[i].ethereum_address == null) {
continue;
}
eth_addresses.push(latestValset.members[i].ethereum_address);
powers.push(latestValset.members[i].power);
powers_sum += latestValset.members[i].power;
}
// 66% of uint32_max
let vote_power = 2834678415;
if (powers_sum < vote_power) {
console.log("Refusing to deploy! Incorrect power! Please inspect the validator set below")
console.log("If less than 66% of the current voting power has unset Ethereum Addresses we refuse to deploy")
console.log(latestValset)
exit(1)
}
const gravity = (await factory.deploy(
// todo generate this randomly at deployment time that way we can avoid
// anything but intentional conflicts
gravityId,
eth_addresses,
powers,
overrides
)) as Gravity;
await gravity.deployed();
console.log("Gravity deployed at Address - ", gravity.address);
await submitGravityAddress(gravity.address);
console.log("Starting Gravity ERC721 contract deploy");
const { abi: abiERC721, bytecode: bytecodeERC721 } = getContractArtifacts(args["contractERC721"]);
const factoryERC721 = new ethers.ContractFactory(abiERC721, bytecodeERC721, wallet);
const gravityERC721 = (await factoryERC721.deploy(
gravity.address
) as GravityERC721);
await gravityERC721.deployed();
console.log("GravityERC721 deployed at Address - ", gravityERC721.address);
}
function getContractArtifacts(path: string): { bytecode: string; abi: string } {
var { bytecode, abi } = JSON.parse(fs.readFileSync(path, "utf8").toString());
return { bytecode, abi };
}
const decode = (str: string): string => Buffer.from(str, 'base64').toString('binary');
async function getLatestValset(): Promise<Valset> {
let block_height_request_string = args["cosmos-node"] + '/status';
let block_height_response = await axios.get(block_height_request_string);
let info: StatusWrapper = await block_height_response.data;
let block_height = info.result.sync_info.latest_block_height;
if (info.result.sync_info.catching_up) {
console.log("This node is still syncing! You can not deploy using this validator set!");
exit(1);
}
let request_string = args["cosmos-node"] + "/abci_query"
let params = {
params: {
path: "\"/custom/gravity/currentValset/\"",
height: block_height,
prove: "false",
}
};
let response = await axios.get(request_string, params);
let valsets: ABCIWrapper = await response.data;
// if in test mode retry the request as needed in some cases
// the cosmos nodes do not start in time
var startTime = new Date();
if (args["test-mode"] == "True" || args["test-mode"] == "true") {
var success = false;
while (valsets.result.response.value == null) {
var present = new Date();
var timeDiff: number = present.getTime() - startTime.getTime();
timeDiff = timeDiff / 1000
response = await axios.get(request_string,
params);
valsets = await response.data;
if (timeDiff > 600) {
console.log("Could not contact Cosmos ABCI after 10 minutes, check the URL!")
exit(1)
}
await sleep(1000);
}
}
console.log(decode(valsets.result.response.value));
let valset: ValsetTypeWrapper = JSON.parse(decode(valsets.result.response.value))
return valset.value;
}
async function getGravityId(): Promise<string> {
let block_height_request_string = args["cosmos-node"] + '/status';
let block_height_response = await axios.get(block_height_request_string);
let info: StatusWrapper = await block_height_response.data;
let block_height = info.result.sync_info.latest_block_height;
if (info.result.sync_info.catching_up) {
console.log("This node is still syncing! You can not deploy using this gravityID!");
exit(1);
}
let request_string = args["cosmos-node"] + "/abci_query"
let params = {
params: {
path: "\"/custom/gravity/gravityID/\"",
height: block_height,
prove: "false",
}
};
let response = await axios.get(request_string,
params);
let gravityIDABCIResponse: ABCIWrapper = await response.data;
// if in test mode retry the request as needed in some cases
// the cosmos nodes do not start in time
var startTime = new Date();
if (args["test-mode"] == "True" || args["test-mode"] == "true") {
var success = false;
while (gravityIDABCIResponse.result.response.value == null) {
var present = new Date();
var timeDiff: number = present.getTime() - startTime.getTime();
timeDiff = timeDiff / 1000
response = await axios.get(request_string,
params);
gravityIDABCIResponse = await response.data;
if (timeDiff > 600) {
console.log("Could not contact Cosmos ABCI after 10 minutes, check the URL!")
exit(1)
}
await sleep(1000);
}
}
let gravityID: string = JSON.parse(decode(gravityIDABCIResponse.result.response.value))
return gravityID;
}
async function submitGravityAddress(address: string) { }
async function main() {
await deploy();
}
function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
main();