This repository has been archived by the owner on Mar 17, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 94
/
utils.js
247 lines (222 loc) · 7.21 KB
/
utils.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
const fs = require("fs");
const parser = require("@solidity-parser/parser");
const request = require("request-promise-native");
const path = require("path");
const read = require("fs-readdir-recursive");
const colors = require("colors/safe");
const log = console.log;
const utils = {
/**
* Expresses gas usage as a nation-state currency price
* @param {Number} gas gas used
* @param {Number} ethPrice e.g chf/eth
* @param {Number} gasPrice in wei e.g 5000000000 (5 gwei)
* @return {Number} cost of gas used (0.00)
*/
gasToCost: function(gas, ethPrice, gasPrice) {
ethPrice = parseFloat(ethPrice);
gasPrice = parseInt(gasPrice);
return ((gasPrice / 1e9) * gas * ethPrice).toFixed(2);
},
/**
* Expresses gas usage as a % of the block gasLimit. Source: NeuFund (see issues)
* @param {Number} gasUsed gas value
* @param {Number} blockLimit gas limit of a block
* @return {Number} percent (0.0)
*/
gasToPercentOfLimit: function(gasUsed, blockLimit) {
return Math.round((1000 * gasUsed) / blockLimit) / 10;
},
/**
* Generates id for a GasData.methods entry from the input of a web3.eth.getTransaction
* and a contract name
* @param {String} code hex data
* @return {String} id
*/
getMethodID: function(contractName, code) {
return contractName + "_" + code.slice(2, 10);
},
/**
* Extracts solc settings and version info from solidity metadata
* @param {Object} metadata solidity metadata
* @return {Object} {version, optimizer, runs}
*/
getSolcInfo: function(metadata) {
const missing = "----";
const info = {
version: missing,
optimizer: missing,
runs: missing
};
if (metadata) {
info.version = metadata.compiler.version;
info.optimizer = metadata.settings.optimizer.enabled;
info.runs = metadata.settings.optimizer.runs;
}
return info;
},
/**
* Return true if transaction input and bytecode are same, ignoring library link code.
* @param {String} code
* @return {Bool}
*/
matchBinaries: function(input, bytecode) {
const regExp = utils.bytecodeToBytecodeRegex(bytecode);
return input.match(regExp) !== null;
},
/**
* Generate a regular expression string which is library link agnostic so we can match
* linked bytecode deployment transaction inputs to the evm.bytecode solc output.
* @param {String} bytecode
* @return {String}
*/
bytecodeToBytecodeRegex: function(bytecode = "") {
const bytecodeRegex = bytecode
.replace(/__.{38}/g, ".{40}")
.replace(/73f{40}/g, ".{42}");
// HACK: Node regexes can't be longer that 32767 characters.
// Contracts bytecode can. We just truncate the regexes. It's safe in practice.
const MAX_REGEX_LENGTH = 32767;
const truncatedBytecodeRegex = bytecodeRegex.slice(0, MAX_REGEX_LENGTH);
return truncatedBytecodeRegex;
},
/**
* Parses files for contract names
* @param {String} filePath path to file
* @return {String[]} contract names
*/
getContractNames: function(filePath) {
const names = [];
const code = fs.readFileSync(filePath, "utf-8");
let ast;
try {
ast = parser.parse(code, { tolerant: true });
} catch (err) {
utils.warnParser(filePath, err);
return names;
}
parser.visit(ast, {
ContractDefinition: function(node) {
names.push(node.name);
}
});
return names;
},
/**
* Message for un-parseable files
* @param {String} filePath
* @param {Error} err
* @return {void}
*/
warnParser: function(filePath, err) {
log();
log(colors.red(`>>>>> WARNING <<<<<<`));
log(
`Failed to parse file: "${filePath}". No data will collected for its contract(s).`
);
log(
`NB: some Solidity 0.6.x syntax is not supported by the JS parser yet.`
);
log(
`Please report the error below to github.com/consensys/solidity-parser-antlr`
);
log(colors.red(`>>>>>>>>>>>>>>>>>>>>`));
log(colors.red(`${err}`));
log();
},
/**
* Message for un-parseable ABI (ethers)
* @param {String} name contract name
* @param {Error} err
* @return {void}
*/
warnEthers: function(name, err) {
log();
log(colors.red(`>>>>> WARNING <<<<<<`));
log(
`Failed to parse ABI for contract: "${name}". (Its method data will not be collected).`
);
log(
`NB: Some Solidity 0.6.x syntax is not supported by Ethers.js V5 AbiCoder yet.`
);
log(`Please report the error below to github.com/ethers-io/ethers.js`);
log(colors.red(`>>>>>>>>>>>>>>>>>>>>`));
log(colors.red(`${err}`));
log();
},
/**
* Converts hex gas to decimal
* @param {Number} val hex gas returned by RPC
* @return {Number} decimal gas consumed by human eyes.
*/
gas: function(val) {
return parseInt(val, 16);
},
/**
* Fetches gasPrices from ethgasstation (defaults to the lowest safe gas price)
* and current market value of eth in currency specified by the config from
* coinmarketcap (defaults to euros). Sets config.ethPrice, config.gasPrice unless these
* are already set as constants in the reporter options
* @param {Object} config
*/
setGasAndPriceRates: async function(config) {
const ethgasstation = `https://ethgasstation.info/json/ethgasAPI.json`;
const coinmarketcap =
`https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/` +
`latest?symbol=ETH&CMC_PRO_API_KEY=${config.coinmarketcap}&convert=`;
const currencyKey = config.currency.toUpperCase();
const currencyPath = `${coinmarketcap}${currencyKey}`;
// Currency market data: coinmarketcap
if (!config.ethPrice) {
try {
let response = await request.get(currencyPath);
response = JSON.parse(response);
config.ethPrice = response.data.ETH.quote[currencyKey].price.toFixed(2);
} catch (error) {
config.ethPrice = null;
}
}
// Gas price data: ethgasstation
if (!config.gasPrice) {
try {
let response = await request.get(ethgasstation);
response = JSON.parse(response);
config.gasPrice = Math.round(response.safeLow / 10);
} catch (error) {
config.gasPrice = config.defaultGasPrice;
}
}
},
listSolidityFiles(srcPath) {
let base = `./${srcPath}/`;
if (process.platform === "win32") {
base = base.replace(/\\/g, "/");
}
const paths = read(base)
.filter(file => path.extname(file) === ".sol")
.map(file => base + file);
return paths;
},
/**
* Loads and parses Solidity files, returning a filtered array of contract names.
* @return {string[]}
*/
parseSoliditySources(config) {
const names = [];
const files = utils.listSolidityFiles(config.srcPath);
files.forEach(file => {
const namesForFile = utils.getContractNames(file);
const filtered = namesForFile.filter(
name => !config.excludeContracts.includes(name)
);
filtered.forEach(item => names.push(item));
});
return names;
},
// Debugging helper
pretty: function(msg, obj) {
console.log(`<------ ${msg} ------>\n` + JSON.stringify(obj, null, " "));
console.log(`<------- END -------->\n`);
}
};
module.exports = utils;