-
Notifications
You must be signed in to change notification settings - Fork 2
/
deployer.js
268 lines (233 loc) · 8.56 KB
/
deployer.js
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
const ganache = require('ganache-cli');
const server = ganache.server();
const fs = require('fs');
const solc = require('solc');
const Web3 = require('web3');
/**
* Builds the ABI for the given solidity contract file.
*
* @param contractFile the contract file for the ABI.
* @return {any} the ABI for the given solidity contract file.
*/
exports.buildABI = function(contractFile) {
const contractMeta = solveContract(contractFile);
return contractMeta.abi;
};
/**
* Compile the given file and return an object that encapsulates
* the ABI and the copiled contract bytecode.
*
* @param contractFile the contract file to be considered.
* @return {{abi: any, bytecode: (contractOutput.evm.bytecode|{object, opcodes, sourceMap, linkReferences})}}
*/
function solveContract(contractFile) {
if (!fs.existsSync(contractFile)) {
throw Error("Can not read the contract file.");
}
const input = fs.readFileSync(contractFile);
const output = solc.compile(input.toString(), 1);
const contractKey = Object.keys(output.contracts)[0];
const bytecode = output.contracts[contractKey].bytecode;
const abi = JSON.parse(output.contracts[contractKey].interface);
return {abi: abi, bytecode: bytecode};
}
var port;
exports.startWeb3 = function(host, port, protocol) {
return new Promise(function(resolve, reject) {
if (!port) {
reject(Error('No port information is available.'));
}
if (port === this.port) {
reject(Error('The port is already allocated.'));
}
this.port = port;
server.listen(port);
const web3URL = protocol + '://' + host + ':' + port;
let web3;
if (protocol === 'http') {
web3 = new Web3(new Web3.providers.HttpProvider(web3URL));
} else if (protocol === 'ws') {
web3 = new Web3(new Web3.providers.WebsocketProvider(web3URL));
}
console.log('Ganache runs on : %s', web3URL);
resolve(web3);
});
};
exports.deployContract = function(web3, contractFile, contractArguments, gasPrice) {
if (!web3) {
throw Error("The web3 client can not be undefined.");
}
if (!contractFile) {
throw Error("Can the contract file can not be undefined.");
}
if (!fs.existsSync(contractFile)) {
throw Error("Can not read the contract file.");
}
if (!gasPrice) {
throw Error("The gas price can not be undefined.");
}
contractArguments = contractArguments ? contractArguments : [];
const eth = web3.eth;
return new Promise(function(resolve, reject) {
eth.getAccounts().then(function(accounts) {
if (!accounts || accounts.length < 1) {
console.error('No accounts are available, the contract %s *WAS NOT* deployed.', contractFile);
reject(Error('No accounts are available.'));
}
const contractMeta = solveContract(contractFile);
const bytecode = contractMeta.bytecode;
const abi = contractMeta.abi;
new eth.Contract(abi).deploy({
data: bytecode,
arguments: contractArguments
})
.estimateGas((error, gasAmount) => {
if (error) {
console.error(error);
reject(error);
}
const account = accounts[0].toLowerCase();
const fromJSON = {
from: account,
gas: gasAmount,
gasPrice: gasPrice
};
// // TODO: find a better way to find the estimated gas
new eth.Contract(abi).deploy({
data: bytecode,
arguments: contractArguments
}).send(fromJSON, function(error, transactionHash) {
if (error) {
console.error(error);
reject(error);
}
if (!transactionHash) {
console.error('The contract %s *WAS NOT* deployed on the account %s.', contractFile, account);
reject(Error('The contract *WAS NOT* deployed'));
}
eth.getTransactionReceipt(transactionHash).then(function (transactionReceipt) {
if (!transactionReceipt) {
console.error('No transaction receipt for the transation, the contract *WAS NOT* deployed.');
reject(Error('No transaction receipt for the transation.'));
}
const contractAddress = transactionReceipt.contractAddress;
if (!contractAddress) {
console.error('No contract address found, the contract *WAS NOT* deployed.');
reject(Error('No contract address found.'));
}
const result = {
owner:account,
transactionHash: transactionHash,
gas: gasAmount,
gasPrice: fromJSON.gasPrice,
contract: {
file: contractFile,
address: contractAddress,
abi: abi
},
accountToKey: {}
};
const ganacheState = server.provider.manager.state;
const ganacheAccounts = ganacheState.accounts;
const ganacheAddresses = Object.keys(ganacheAccounts);
ganacheAddresses.forEach(function(address, index) {
const key = '0x' + ganacheAccounts[address].secretKey.toString("hex").toLowerCase();
result.accountToKey[address] = key;
});
resolve(result);
});
});
});
});
});
};
exports.closeWeb3 = () => {
server.close();
console.log("Ganache node stops.");
};
/**
* Returns an array with all the test accounts encapsulated in the given deploy report.
*
* @param deployReport the involved deploy report.
* @returns {string[]} all the accounts from the given deploy report.
*/
exports.getAllAccounts = function(deployReport) {
return Object.keys(deployReport.accountToKey);
};
/**
* Proves if a given account exists or not in in the given deploy report.
*
* @param deployReport the involved deploy report.
* @param account {string} the account presence to be proven.
* @returns {boolean} true if the given account exists in the deploy report.
*/
exports.accountExist = function(deployReport, account) {
// use _ for contains prove
return Object.keys(deployReport.accountToKey).indexOf(account) != -1;
};
/**
* Returns the private key for the given account.
* If the account is not preset in the given deploy report then this method retuns undefined.
*
* @param deployReport the involved deploy report.
* @param account {string} the account to be serached.
* @returns {string} the private key associated to the given account or undefined
* if the deploy report does not contains the given account.
*/
exports.getKey = function(deployReport, account) {
return deployReport.accountToKey[account];
};
/**
* Returns the private key associated to the owner.
*
* @param deployReport the involved deploy report.
* @returns {string} the private key associated to the owner account.
*/
exports.getKeyForOwner = function(deployReport) {
return getKey(deployReport, deployReport.owner);
};
/**
* Encodes a method call for a given contract.
*
* @param web3 the involved web3 instance.
* @param abi the contract ABI.
* @param methodName the method name to be call.
* @param args the arguments for the method, empty array for no arguments.
* @returns {string} the encoded method call.
*/
function encodeFunctionCall(web3, abi, methodName, args) {
const methodAbi = web3.utils._.find(abi, function (item) { return item.name === methodName;});
return web3.eth.abi.encodeFunctionCall(methodAbi, args);
}
/**
* Calls a method on the given contract and returns its results.
*
* @param web3 the involved web3 instance.
* @param abi the contract ABI.
* @param methodName the method name to be call.
* @param args the arguments for the method, empty array for no arguments.
* @param sender actor that call the method.
* @param contractAddress the contract address.
* @returns {Promise<string>} the method result as promise.
*/
exports.callMethod = async function(web3, abi, methodName, args, sender, contractAddress) {
const transaction = {
data: encodeFunctionCall(web3, abi, methodName, args),
from: sender,
gasPrice: '200000000000',
to: contractAddress,
value: 0
};
const gas = await web3.eth.estimateGas(transaction);
transaction.gas = gas;
return web3.eth.call(transaction);
};
/**
* Returns the contract for a given deployment report.
*
* @param deployReport {string} the deployment report to be considered.
* @return {Contract} the contract for the given deployReport.
*/
exports.getContract = function getContract(web3, deployReport) {
return new web3.eth.Contract(deployReport.contract.abi, deployReport.contract.address);
};