1 | import fs from "fs"
|
2 | import path from "path"
|
3 | import sha1 from "sha1"
|
4 | import arrayUniq from "array-uniq";
|
5 | import { TASK_TEST_RUN_MOCHA_TESTS } from "hardhat/builtin-tasks/task-names";
|
6 | import { task, subtask } from "hardhat/config";
|
7 | import { HARDHAT_NETWORK_NAME } from "hardhat/plugins";
|
8 | import { globSync } from "hardhat/internal/util/glob";
|
9 | import {
|
10 | BackwardsCompatibilityProviderAdapter
|
11 | } from "hardhat/internal/core/providers/backwards-compatibility"
|
12 |
|
13 | import {
|
14 | EGRDataCollectionProvider,
|
15 | EGRAsyncApiProvider
|
16 | } from "./providers";
|
17 |
|
18 | import {
|
19 | HardhatArguments,
|
20 | HttpNetworkConfig,
|
21 | NetworkConfig,
|
22 | EthereumProvider,
|
23 | HardhatRuntimeEnvironment,
|
24 | Artifact,
|
25 | Artifacts
|
26 | } from "hardhat/types";
|
27 |
|
28 | import "./type-extensions"
|
29 | import { EthGasReporterConfig, EthGasReporterOutput, RemoteContract } from "./types";
|
30 | import { TASK_GAS_REPORTER_MERGE, TASK_GAS_REPORTER_MERGE_REPORTS } from "./task-names";
|
31 | import { mergeReports } from "./merge-reports";
|
32 |
|
33 | const { parseSoliditySources, setGasAndPriceRates } = require('eth-gas-reporter/lib/utils');
|
34 | const InternalReporterConfig = require('eth-gas-reporter/lib/config');
|
35 |
|
36 | let mochaConfig;
|
37 | let resolvedQualifiedNames: string[]
|
38 | let resolvedRemoteContracts: RemoteContract[] = [];
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 | function shouldSkipContract(qualifiedName: string, skippable: string[]): boolean {
|
47 | for (const item of skippable){
|
48 | if (qualifiedName.includes(item)) return true;
|
49 | }
|
50 | return false;
|
51 | }
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 | function getContracts(artifacts: Artifacts, skippable: string[] = []) : any[] {
|
61 | const contracts = [];
|
62 |
|
63 | for (const qualifiedName of resolvedQualifiedNames) {
|
64 | if (shouldSkipContract(qualifiedName, skippable)){
|
65 | continue;
|
66 | }
|
67 |
|
68 | let name: string;
|
69 | let artifact = artifacts.readArtifactSync(qualifiedName)
|
70 |
|
71 |
|
72 | try {
|
73 | artifact = artifacts.readArtifactSync(artifact.contractName);
|
74 | name = artifact.contractName;
|
75 | } catch (e) {
|
76 | name = qualifiedName;
|
77 | }
|
78 |
|
79 | contracts.push({
|
80 | name: name,
|
81 | artifact: {
|
82 | abi: artifact.abi,
|
83 | bytecode: artifact.bytecode,
|
84 | deployedBytecode: artifact.deployedBytecode
|
85 | }
|
86 | });
|
87 | }
|
88 |
|
89 | for (const remoteContract of resolvedRemoteContracts){
|
90 | contracts.push({
|
91 | name: remoteContract.name,
|
92 | artifact: {
|
93 | abi: remoteContract.abi,
|
94 | bytecode: remoteContract.bytecode,
|
95 | bytecodeHash: remoteContract.bytecodeHash,
|
96 | deployedBytecode: remoteContract.deployedBytecode
|
97 | }
|
98 | })
|
99 | }
|
100 | return contracts;
|
101 | }
|
102 |
|
103 |
|
104 |
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 | function getDefaultOptions(hre: HardhatRuntimeEnvironment): EthGasReporterConfig {
|
112 | const defaultUrl = "http://localhost:8545";
|
113 | const defaultCompiler = hre.config.solidity.compilers[0]
|
114 |
|
115 | let url: any;
|
116 |
|
117 | if ((<HttpNetworkConfig>hre.network.config).url) {
|
118 | url = (<HttpNetworkConfig>hre.network.config).url;
|
119 | } else {
|
120 | url = defaultUrl;
|
121 | }
|
122 |
|
123 | return {
|
124 | enabled: true,
|
125 | url: <string>url,
|
126 | metadata: {
|
127 | compiler: {
|
128 | version: defaultCompiler.version
|
129 | },
|
130 | settings: {
|
131 | optimizer: {
|
132 | enabled: defaultCompiler.settings.optimizer.enabled,
|
133 | runs: defaultCompiler.settings.optimizer.runs
|
134 | }
|
135 | }
|
136 | }
|
137 | }
|
138 | }
|
139 |
|
140 |
|
141 |
|
142 |
|
143 |
|
144 |
|
145 | function getOptions(hre: HardhatRuntimeEnvironment): any {
|
146 | return { ...getDefaultOptions(hre), ...(hre.config as any).gasReporter };
|
147 | }
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 | async function getResolvedRemoteContracts(
|
157 | provider: EGRAsyncApiProvider,
|
158 | remoteContracts: RemoteContract[] = []
|
159 | ) : Promise <RemoteContract[]> {
|
160 | for (const contract of remoteContracts){
|
161 | let code;
|
162 | try {
|
163 | contract.bytecode = await provider.getCode(contract.address);
|
164 | contract.deployedBytecode = contract.bytecode;
|
165 | contract.bytecodeHash = sha1(contract.bytecode);
|
166 | } catch (error){
|
167 | console.log(`Warning: failed to fetch bytecode for remote contract: ${contract.name}`)
|
168 | console.log(`Error was: ${error}\n`);
|
169 | }
|
170 | }
|
171 | return remoteContracts;
|
172 | }
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 |
|
179 | subtask(TASK_TEST_RUN_MOCHA_TESTS).setAction(
|
180 | async (args: any, hre, runSuper) => {
|
181 | let options = getOptions(hre);
|
182 | options.getContracts = getContracts.bind(null, hre.artifacts, options.excludeContracts);
|
183 |
|
184 | if (options.enabled) {
|
185 |
|
186 | options = new InternalReporterConfig(options);
|
187 | await setGasAndPriceRates(options);
|
188 |
|
189 | mochaConfig = hre.config.mocha || {};
|
190 | mochaConfig.reporter = "eth-gas-reporter";
|
191 | mochaConfig.reporterOptions = options;
|
192 |
|
193 | if (hre.network.name === HARDHAT_NETWORK_NAME || options.fast){
|
194 | const wrappedDataProvider= new EGRDataCollectionProvider(hre.network.provider,mochaConfig);
|
195 | hre.network.provider = new BackwardsCompatibilityProviderAdapter(wrappedDataProvider);
|
196 |
|
197 | const asyncProvider = new EGRAsyncApiProvider(hre.network.provider);
|
198 | resolvedRemoteContracts = await getResolvedRemoteContracts(
|
199 | asyncProvider,
|
200 | options.remoteContracts
|
201 | );
|
202 |
|
203 | mochaConfig.reporterOptions.provider = asyncProvider;
|
204 | mochaConfig.reporterOptions.blockLimit = (<any>hre.network.config).blockGasLimit as number;
|
205 | mochaConfig.attachments = {};
|
206 | }
|
207 |
|
208 | hre.config.mocha = mochaConfig;
|
209 | resolvedQualifiedNames = await hre.artifacts.getAllFullyQualifiedNames();
|
210 | }
|
211 |
|
212 | return runSuper();
|
213 | }
|
214 | );
|
215 |
|
216 | subtask(TASK_GAS_REPORTER_MERGE_REPORTS)
|
217 | .addOptionalVariadicPositionalParam(
|
218 | "inputFiles",
|
219 | "Path of several gasReporterOutput.json files to merge",
|
220 | []
|
221 | )
|
222 | .setAction(async ({ inputFiles }: { inputFiles: string[] }) => {
|
223 | const reports = inputFiles.map((input) => JSON.parse(fs.readFileSync(input, "utf-8")));
|
224 | return mergeReports(reports);
|
225 | })
|
226 |
|
227 |
|
228 |
|
229 |
|
230 |
|
231 |
|
232 |
|
233 | task(TASK_GAS_REPORTER_MERGE)
|
234 | .addOptionalParam(
|
235 | "output",
|
236 | "Target file to save the merged report",
|
237 | "gasReporterOutput.json"
|
238 | )
|
239 | .addVariadicPositionalParam(
|
240 | "input",
|
241 | "A list of gasReporterOutput.json files generated by eth-gas-reporter. Files can be defined using glob patterns"
|
242 | )
|
243 | .setAction(async (taskArguments, { run }) => {
|
244 | const output = path.resolve(process.cwd(), taskArguments.output);
|
245 |
|
246 |
|
247 | const inputFiles = arrayUniq<string>(taskArguments.input.map(globSync).flat())
|
248 | .map(inputFile => path.resolve(inputFile));
|
249 |
|
250 | if (inputFiles.length === 0) {
|
251 | throw new Error(`No files found for the given input: ${taskArguments.input.join(" ")}`);
|
252 | }
|
253 |
|
254 | console.log(`Merging ${inputFiles.length} input files:`);
|
255 | inputFiles.forEach(inputFile => {
|
256 | console.log(" - ", inputFile);
|
257 | });
|
258 |
|
259 | console.log("\nOutput: ", output);
|
260 |
|
261 | const result = await run(TASK_GAS_REPORTER_MERGE_REPORTS, { inputFiles });
|
262 |
|
263 | fs.writeFileSync(output, JSON.stringify(result), "utf-8");
|
264 | });
|