hardhat-external-artifacts 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +228 -0
- package/dist/artifacts/converter.d.ts +62 -0
- package/dist/artifacts/converter.d.ts.map +1 -0
- package/dist/artifacts/converter.js +241 -0
- package/dist/artifacts/converter.js.map +1 -0
- package/dist/artifacts/loader.d.ts +7 -0
- package/dist/artifacts/loader.d.ts.map +1 -0
- package/dist/artifacts/loader.js +113 -0
- package/dist/artifacts/loader.js.map +1 -0
- package/dist/artifacts/types.d.ts +110 -0
- package/dist/artifacts/types.d.ts.map +1 -0
- package/dist/artifacts/types.js +7 -0
- package/dist/artifacts/types.js.map +1 -0
- package/dist/hooks/config.d.ts +4 -0
- package/dist/hooks/config.d.ts.map +1 -0
- package/dist/hooks/config.js +23 -0
- package/dist/hooks/config.js.map +1 -0
- package/dist/hooks/network.d.ts +4 -0
- package/dist/hooks/network.d.ts.map +1 -0
- package/dist/hooks/network.js +87 -0
- package/dist/hooks/network.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/type-extensions.d.ts +17 -0
- package/dist/type-extensions.d.ts.map +1 -0
- package/dist/type-extensions.js +2 -0
- package/dist/type-extensions.js.map +1 -0
- package/package.json +65 -0
- package/src/artifacts/converter.ts +346 -0
- package/src/artifacts/loader.ts +148 -0
- package/src/artifacts/types.ts +132 -0
- package/src/hooks/config.ts +43 -0
- package/src/hooks/network.ts +164 -0
- package/src/index.ts +15 -0
- package/src/type-extensions.ts +19 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import type {ExternalArtifact, RichArtifact, LinkReferences} from './types.js';
|
|
2
|
+
import {isRichArtifact} from './types.js';
|
|
3
|
+
|
|
4
|
+
export interface SyntheticCompilation {
|
|
5
|
+
solcVersion: string;
|
|
6
|
+
compilerInput: CompilerInput;
|
|
7
|
+
compilerOutput: CompilerOutput;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface CompilerInput {
|
|
11
|
+
language: string;
|
|
12
|
+
sources: Record<string, {content: string}>;
|
|
13
|
+
settings: {
|
|
14
|
+
optimizer: {enabled: boolean; runs?: number};
|
|
15
|
+
outputSelection: Record<string, Record<string, string[]>>;
|
|
16
|
+
remappings?: string[];
|
|
17
|
+
metadata?: {useLiteralContent?: boolean; bytecodeHash?: string};
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface CompilerOutput {
|
|
22
|
+
sources: Record<string, {id: number; ast: object}>;
|
|
23
|
+
contracts: Record<
|
|
24
|
+
string,
|
|
25
|
+
Record<
|
|
26
|
+
string,
|
|
27
|
+
{
|
|
28
|
+
abi: readonly any[];
|
|
29
|
+
evm: {
|
|
30
|
+
bytecode: BytecodeOutput;
|
|
31
|
+
deployedBytecode: BytecodeOutput;
|
|
32
|
+
methodIdentifiers: Record<string, string>;
|
|
33
|
+
};
|
|
34
|
+
metadata?: string;
|
|
35
|
+
devdoc?: any;
|
|
36
|
+
userdoc?: any;
|
|
37
|
+
storageLayout?: any;
|
|
38
|
+
}
|
|
39
|
+
>
|
|
40
|
+
>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface BytecodeOutput {
|
|
44
|
+
object: string;
|
|
45
|
+
opcodes: string;
|
|
46
|
+
sourceMap: string;
|
|
47
|
+
linkReferences: LinkReferences;
|
|
48
|
+
immutableReferences?: Record<string, Array<{start: number; length: number}>>;
|
|
49
|
+
generatedSources?: any[];
|
|
50
|
+
functionDebugData?: Record<string, any>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Creates a minimal valid AST for a source file.
|
|
55
|
+
* This is needed because Hardhat's contract decoder expects a valid AST structure.
|
|
56
|
+
*/
|
|
57
|
+
function createMinimalAst(
|
|
58
|
+
sourceName: string,
|
|
59
|
+
sourceId: number,
|
|
60
|
+
contracts?: Array<{name: string; nodeId: number}>,
|
|
61
|
+
): object {
|
|
62
|
+
const nodes: object[] = [];
|
|
63
|
+
const exportedSymbols: Record<string, number[]> = {};
|
|
64
|
+
|
|
65
|
+
// Add contract definition nodes if provided
|
|
66
|
+
if (contracts) {
|
|
67
|
+
for (const contract of contracts) {
|
|
68
|
+
nodes.push({
|
|
69
|
+
nodeType: 'ContractDefinition',
|
|
70
|
+
id: contract.nodeId,
|
|
71
|
+
src: `0:0:${sourceId}`,
|
|
72
|
+
name: contract.name,
|
|
73
|
+
contractKind: 'contract',
|
|
74
|
+
abstract: false,
|
|
75
|
+
fullyImplemented: true,
|
|
76
|
+
linearizedBaseContracts: [contract.nodeId],
|
|
77
|
+
nodes: [],
|
|
78
|
+
scope: sourceId,
|
|
79
|
+
});
|
|
80
|
+
exportedSymbols[contract.name] = [contract.nodeId];
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
nodeType: 'SourceUnit',
|
|
86
|
+
src: `0:0:${sourceId}`,
|
|
87
|
+
id: sourceId,
|
|
88
|
+
absolutePath: sourceName,
|
|
89
|
+
exportedSymbols,
|
|
90
|
+
nodes,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Convert artifacts to compilation format.
|
|
96
|
+
* If artifacts are "rich" (have solcInput), use the embedded data.
|
|
97
|
+
* Otherwise, synthesize a minimal compilation.
|
|
98
|
+
*/
|
|
99
|
+
export function artifactsToCompilations(
|
|
100
|
+
artifacts: ExternalArtifact[],
|
|
101
|
+
defaultSolcVersion: string,
|
|
102
|
+
): SyntheticCompilation[] {
|
|
103
|
+
// Group artifacts by whether they have solcInput
|
|
104
|
+
const richArtifacts = artifacts.filter(isRichArtifact);
|
|
105
|
+
const simpleArtifacts = artifacts.filter((a) => !isRichArtifact(a));
|
|
106
|
+
|
|
107
|
+
const compilations: SyntheticCompilation[] = [];
|
|
108
|
+
|
|
109
|
+
// Process rich artifacts - these have embedded solcInput
|
|
110
|
+
for (const artifact of richArtifacts) {
|
|
111
|
+
const compilation = richArtifactToCompilation(artifact);
|
|
112
|
+
if (compilation) {
|
|
113
|
+
compilations.push(compilation);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Process simple artifacts - synthesize compilation
|
|
118
|
+
if (simpleArtifacts.length > 0) {
|
|
119
|
+
compilations.push(
|
|
120
|
+
synthesizeCompilation(simpleArtifacts, defaultSolcVersion),
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return compilations;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Convert a rich artifact (with embedded solcInput) to a compilation.
|
|
129
|
+
* Uses the embedded solcInput directly for maximum fidelity.
|
|
130
|
+
*/
|
|
131
|
+
function richArtifactToCompilation(
|
|
132
|
+
artifact: RichArtifact,
|
|
133
|
+
): SyntheticCompilation | null {
|
|
134
|
+
if (!artifact.solcInput) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Parse the embedded solcInput
|
|
139
|
+
const compilerInput: CompilerInput = JSON.parse(artifact.solcInput);
|
|
140
|
+
|
|
141
|
+
// Extract solc version from metadata
|
|
142
|
+
let solcVersion = '0.8.20'; // Default
|
|
143
|
+
if (artifact.metadata) {
|
|
144
|
+
try {
|
|
145
|
+
const metadata = JSON.parse(artifact.metadata);
|
|
146
|
+
if (metadata.compiler?.version) {
|
|
147
|
+
// Format: "0.8.10+commit.fc410830" -> extract "0.8.10"
|
|
148
|
+
solcVersion = metadata.compiler.version.split('+')[0];
|
|
149
|
+
}
|
|
150
|
+
} catch {
|
|
151
|
+
// Ignore parsing errors, use default
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Build compiler output from the artifact
|
|
156
|
+
// Only include sources that we have contract data for
|
|
157
|
+
// Including empty sources/contracts can cause EDR selector fixup issues
|
|
158
|
+
const compilerOutput: CompilerOutput = {
|
|
159
|
+
sources: {},
|
|
160
|
+
contracts: {},
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const sourceName = artifact.sourceName;
|
|
164
|
+
|
|
165
|
+
// Track source IDs for all input sources (needed for consistent source indexing)
|
|
166
|
+
let sourceId = 0;
|
|
167
|
+
const sourceIds: Record<string, number> = {};
|
|
168
|
+
for (const srcName of Object.keys(compilerInput.sources)) {
|
|
169
|
+
sourceIds[srcName] = sourceId++;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Only include the source that contains our contract
|
|
173
|
+
const contractSourceId = sourceIds[sourceName] ?? sourceId++;
|
|
174
|
+
const contractNodeId = contractSourceId + 1000; // Use an offset to avoid ID conflicts
|
|
175
|
+
|
|
176
|
+
compilerOutput.sources[sourceName] = {
|
|
177
|
+
id: contractSourceId,
|
|
178
|
+
ast: createMinimalAst(sourceName, contractSourceId, [
|
|
179
|
+
{name: artifact.contractName, nodeId: contractNodeId},
|
|
180
|
+
]),
|
|
181
|
+
};
|
|
182
|
+
compilerOutput.contracts[sourceName] = {};
|
|
183
|
+
|
|
184
|
+
// Ensure the source is in compilerInput as well
|
|
185
|
+
if (!compilerInput.sources[sourceName]) {
|
|
186
|
+
compilerInput.sources[sourceName] = {content: ''};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Build bytecode output, ensuring proper format
|
|
190
|
+
// Standard solc output has bytecode.object without 0x prefix
|
|
191
|
+
const bytecode: BytecodeOutput = {
|
|
192
|
+
object: stripHexPrefix(
|
|
193
|
+
artifact.evm?.bytecode?.object ?? artifact.bytecode ?? '0x',
|
|
194
|
+
),
|
|
195
|
+
opcodes: artifact.evm?.bytecode?.opcodes ?? '',
|
|
196
|
+
sourceMap: artifact.evm?.bytecode?.sourceMap ?? '',
|
|
197
|
+
linkReferences:
|
|
198
|
+
artifact.evm?.bytecode?.linkReferences ?? artifact.linkReferences ?? {},
|
|
199
|
+
generatedSources: artifact.evm?.bytecode?.generatedSources,
|
|
200
|
+
functionDebugData: artifact.evm?.bytecode?.functionDebugData,
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const deployedBytecode: BytecodeOutput = {
|
|
204
|
+
object: stripHexPrefix(
|
|
205
|
+
artifact.evm?.deployedBytecode?.object ??
|
|
206
|
+
artifact.deployedBytecode ??
|
|
207
|
+
'0x',
|
|
208
|
+
),
|
|
209
|
+
opcodes: artifact.evm?.deployedBytecode?.opcodes ?? '',
|
|
210
|
+
sourceMap: artifact.evm?.deployedBytecode?.sourceMap ?? '',
|
|
211
|
+
linkReferences:
|
|
212
|
+
artifact.evm?.deployedBytecode?.linkReferences ??
|
|
213
|
+
artifact.deployedLinkReferences ??
|
|
214
|
+
{},
|
|
215
|
+
immutableReferences:
|
|
216
|
+
artifact.evm?.deployedBytecode?.immutableReferences ?? {},
|
|
217
|
+
generatedSources: artifact.evm?.deployedBytecode?.generatedSources,
|
|
218
|
+
functionDebugData: artifact.evm?.deployedBytecode?.functionDebugData,
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// IMPORTANT: Do NOT provide method identifiers - let EDR compute them
|
|
222
|
+
// EDR has internal "selector fixup" logic for function overloading that can fail
|
|
223
|
+
// if we provide method identifiers that it then tries to reconcile with AST info.
|
|
224
|
+
// The error "Failed to fix up the selector for ... #supportsInterface" happens
|
|
225
|
+
// when EDR can't match provided selectors with overloaded functions.
|
|
226
|
+
// By providing an empty object, EDR will compute selectors from the ABI directly.
|
|
227
|
+
const methodIdentifiers: Record<string, string> = {};
|
|
228
|
+
|
|
229
|
+
compilerOutput.contracts[sourceName][artifact.contractName] = {
|
|
230
|
+
abi: artifact.abi,
|
|
231
|
+
evm: {
|
|
232
|
+
bytecode,
|
|
233
|
+
deployedBytecode,
|
|
234
|
+
methodIdentifiers,
|
|
235
|
+
},
|
|
236
|
+
metadata: artifact.metadata,
|
|
237
|
+
devdoc: artifact.devdoc,
|
|
238
|
+
userdoc: artifact.userdoc,
|
|
239
|
+
storageLayout: artifact.storageLayout,
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
solcVersion,
|
|
244
|
+
compilerInput,
|
|
245
|
+
compilerOutput,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Synthesize a minimal compilation from simple artifacts.
|
|
251
|
+
* Used when artifacts don't have embedded solcInput.
|
|
252
|
+
*/
|
|
253
|
+
function synthesizeCompilation(
|
|
254
|
+
artifacts: ExternalArtifact[],
|
|
255
|
+
solcVersion: string,
|
|
256
|
+
): SyntheticCompilation {
|
|
257
|
+
const sources: CompilerInput['sources'] = {};
|
|
258
|
+
const outputSources: CompilerOutput['sources'] = {};
|
|
259
|
+
const contracts: CompilerOutput['contracts'] = {};
|
|
260
|
+
|
|
261
|
+
// First, group artifacts by source
|
|
262
|
+
const contractsBySource: Record<
|
|
263
|
+
string,
|
|
264
|
+
Array<{name: string; artifact: ExternalArtifact}>
|
|
265
|
+
> = {};
|
|
266
|
+
for (const artifact of artifacts) {
|
|
267
|
+
if (!contractsBySource[artifact.sourceName]) {
|
|
268
|
+
contractsBySource[artifact.sourceName] = [];
|
|
269
|
+
}
|
|
270
|
+
contractsBySource[artifact.sourceName].push({
|
|
271
|
+
name: artifact.contractName,
|
|
272
|
+
artifact,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Track IDs for AST nodes
|
|
277
|
+
let nextId = 0;
|
|
278
|
+
|
|
279
|
+
// Create sources with contract definitions in AST
|
|
280
|
+
for (const [sourceName, sourceContracts] of Object.entries(
|
|
281
|
+
contractsBySource,
|
|
282
|
+
)) {
|
|
283
|
+
const sourceId = nextId++;
|
|
284
|
+
sources[sourceName] = {content: ''};
|
|
285
|
+
contracts[sourceName] = {};
|
|
286
|
+
|
|
287
|
+
// Create contract nodes for AST
|
|
288
|
+
const contractNodes: Array<{name: string; nodeId: number}> = [];
|
|
289
|
+
for (const {name} of sourceContracts) {
|
|
290
|
+
const nodeId = nextId++;
|
|
291
|
+
contractNodes.push({name, nodeId});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
outputSources[sourceName] = {
|
|
295
|
+
id: sourceId,
|
|
296
|
+
ast: createMinimalAst(sourceName, sourceId, contractNodes),
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// Add contract outputs
|
|
300
|
+
for (const {name, artifact} of sourceContracts) {
|
|
301
|
+
contracts[sourceName][name] = {
|
|
302
|
+
abi: artifact.abi,
|
|
303
|
+
evm: {
|
|
304
|
+
bytecode: {
|
|
305
|
+
object: stripHexPrefix(artifact.bytecode),
|
|
306
|
+
opcodes: '',
|
|
307
|
+
sourceMap: '',
|
|
308
|
+
linkReferences: artifact.linkReferences ?? {},
|
|
309
|
+
},
|
|
310
|
+
deployedBytecode: {
|
|
311
|
+
object: stripHexPrefix(artifact.deployedBytecode),
|
|
312
|
+
opcodes: '',
|
|
313
|
+
sourceMap: '',
|
|
314
|
+
linkReferences: artifact.deployedLinkReferences ?? {},
|
|
315
|
+
immutableReferences: {},
|
|
316
|
+
},
|
|
317
|
+
// Empty object - let EDR compute selectors to avoid selector fixup issues
|
|
318
|
+
// with overloaded functions (consistent with richArtifactToCompilation)
|
|
319
|
+
methodIdentifiers: {},
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
solcVersion,
|
|
327
|
+
compilerInput: {
|
|
328
|
+
language: 'Solidity',
|
|
329
|
+
sources,
|
|
330
|
+
settings: {
|
|
331
|
+
optimizer: {enabled: false},
|
|
332
|
+
outputSelection: {
|
|
333
|
+
'*': {'*': ['abi', 'evm.bytecode', 'evm.deployedBytecode']},
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
compilerOutput: {
|
|
338
|
+
sources: outputSources,
|
|
339
|
+
contracts,
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function stripHexPrefix(hex: string): string {
|
|
345
|
+
return hex.startsWith('0x') ? hex.slice(2) : hex;
|
|
346
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExternalArtifact,
|
|
3
|
+
RichArtifact,
|
|
4
|
+
ExternalArtifactsConfig,
|
|
5
|
+
} from './types.js';
|
|
6
|
+
import {
|
|
7
|
+
readJsonFile,
|
|
8
|
+
getAllFilesMatching,
|
|
9
|
+
} from '@nomicfoundation/hardhat-utils/fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import fs from 'node:fs/promises';
|
|
12
|
+
|
|
13
|
+
export class ArtifactLoader {
|
|
14
|
+
readonly #config: ExternalArtifactsConfig;
|
|
15
|
+
readonly #projectRoot: string;
|
|
16
|
+
|
|
17
|
+
constructor(config: ExternalArtifactsConfig, projectRoot: string) {
|
|
18
|
+
this.#config = config;
|
|
19
|
+
this.#projectRoot = projectRoot;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async loadAll(): Promise<ExternalArtifact[]> {
|
|
23
|
+
const artifacts: ExternalArtifact[] = [];
|
|
24
|
+
|
|
25
|
+
// Load from paths
|
|
26
|
+
if (this.#config.paths) {
|
|
27
|
+
for (const pathOrGlob of this.#config.paths) {
|
|
28
|
+
const absolutePath = path.resolve(this.#projectRoot, pathOrGlob);
|
|
29
|
+
artifacts.push(...(await this.#loadFromPath(absolutePath)));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Load from resolver function
|
|
34
|
+
if (this.#config.resolver) {
|
|
35
|
+
const resolvedArtifacts = await this.#config.resolver();
|
|
36
|
+
artifacts.push(...resolvedArtifacts);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return artifacts;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async #loadFromPath(absolutePath: string): Promise<ExternalArtifact[]> {
|
|
43
|
+
let stat: Awaited<ReturnType<typeof fs.stat>>;
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
stat = await fs.stat(absolutePath);
|
|
47
|
+
} catch {
|
|
48
|
+
// Path doesn't exist, return empty array
|
|
49
|
+
if (this.#config.warnOnInvalidArtifacts !== false) {
|
|
50
|
+
console.warn(
|
|
51
|
+
`[hardhat-external-artifacts] Path not found: ${absolutePath}`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (stat.isFile()) {
|
|
58
|
+
try {
|
|
59
|
+
return [await this.#loadArtifactFile(absolutePath)];
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (this.#config.warnOnInvalidArtifacts !== false) {
|
|
62
|
+
console.warn(
|
|
63
|
+
`[hardhat-external-artifacts] Failed to load artifact: ${absolutePath}`,
|
|
64
|
+
error,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (stat.isDirectory()) {
|
|
72
|
+
// Support both .json and .ts artifact files
|
|
73
|
+
const files = await getAllFilesMatching(absolutePath, (p) =>
|
|
74
|
+
p.endsWith('.json'),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const loadedArtifacts: ExternalArtifact[] = [];
|
|
78
|
+
for (const file of files) {
|
|
79
|
+
try {
|
|
80
|
+
loadedArtifacts.push(await this.#loadArtifactFile(file));
|
|
81
|
+
} catch (error) {
|
|
82
|
+
if (this.#config.warnOnInvalidArtifacts !== false) {
|
|
83
|
+
console.warn(
|
|
84
|
+
`[hardhat-external-artifacts] Failed to load artifact: ${file}`,
|
|
85
|
+
error,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return loadedArtifacts;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async #loadArtifactFile(filePath: string): Promise<ExternalArtifact> {
|
|
97
|
+
const content = await readJsonFile(filePath);
|
|
98
|
+
return this.#normalizeArtifact(content, filePath);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#normalizeArtifact(raw: any, source: string): ExternalArtifact {
|
|
102
|
+
// Validate required fields - only ABI is truly required
|
|
103
|
+
if (!raw.abi || !Array.isArray(raw.abi)) {
|
|
104
|
+
throw new Error(
|
|
105
|
+
`Artifact from ${source} is missing required field 'abi' or it's not an array`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Infer contractName from filename if not provided
|
|
110
|
+
// e.g., "/path/to/EIP173Proxy.json" -> "EIP173Proxy"
|
|
111
|
+
let contractName = raw.contractName;
|
|
112
|
+
if (!contractName || typeof contractName !== 'string') {
|
|
113
|
+
const filename = path.basename(source, '.json');
|
|
114
|
+
contractName = filename;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Infer sourceName from filename if not provided
|
|
118
|
+
// e.g., "EIP173Proxy" -> "external/EIP173Proxy.sol"
|
|
119
|
+
let sourceName = raw.sourceName;
|
|
120
|
+
if (!sourceName || typeof sourceName !== 'string') {
|
|
121
|
+
sourceName = `external/${contractName}.sol`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Base artifact fields - required
|
|
125
|
+
const artifact: ExternalArtifact = {
|
|
126
|
+
contractName,
|
|
127
|
+
sourceName,
|
|
128
|
+
abi: raw.abi,
|
|
129
|
+
bytecode: raw.bytecode ?? '0x',
|
|
130
|
+
deployedBytecode: raw.deployedBytecode ?? '0x',
|
|
131
|
+
linkReferences: raw.linkReferences ?? {},
|
|
132
|
+
deployedLinkReferences: raw.deployedLinkReferences ?? {},
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Check if this is a "rich" artifact with embedded solcInput
|
|
136
|
+
if (raw.solcInput) {
|
|
137
|
+
// Rich artifact - has full compilation data
|
|
138
|
+
(artifact as RichArtifact).solcInput = raw.solcInput;
|
|
139
|
+
(artifact as RichArtifact).metadata = raw.metadata;
|
|
140
|
+
(artifact as RichArtifact).evm = raw.evm;
|
|
141
|
+
(artifact as RichArtifact).devdoc = raw.devdoc;
|
|
142
|
+
(artifact as RichArtifact).userdoc = raw.userdoc;
|
|
143
|
+
(artifact as RichArtifact).storageLayout = raw.storageLayout;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return artifact;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A minimal artifact format that can be provided externally.
|
|
3
|
+
* Supports both Hardhat v2 and v3 formats.
|
|
4
|
+
*/
|
|
5
|
+
export interface ExternalArtifact {
|
|
6
|
+
/** Contract name */
|
|
7
|
+
contractName: string;
|
|
8
|
+
|
|
9
|
+
/** Source file path/identifier */
|
|
10
|
+
sourceName: string;
|
|
11
|
+
|
|
12
|
+
/** Contract ABI */
|
|
13
|
+
abi: readonly any[];
|
|
14
|
+
|
|
15
|
+
/** Deployment bytecode (0x-prefixed) */
|
|
16
|
+
bytecode: string;
|
|
17
|
+
|
|
18
|
+
/** Deployed/runtime bytecode (0x-prefixed) */
|
|
19
|
+
deployedBytecode: string;
|
|
20
|
+
|
|
21
|
+
/** Library link references for deployment bytecode */
|
|
22
|
+
linkReferences?: LinkReferences;
|
|
23
|
+
|
|
24
|
+
/** Library link references for deployed bytecode */
|
|
25
|
+
deployedLinkReferences?: LinkReferences;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A rich artifact that includes embedded solcInput and full compilation data.
|
|
30
|
+
* This is the format from hardhat-deploy or other tools that preserve
|
|
31
|
+
* the full compilation output.
|
|
32
|
+
*/
|
|
33
|
+
export interface RichArtifact extends ExternalArtifact {
|
|
34
|
+
/** The full solc compiler input JSON (stringified) */
|
|
35
|
+
solcInput?: string;
|
|
36
|
+
|
|
37
|
+
/** Contract metadata JSON (contains solc version, settings, etc.) */
|
|
38
|
+
metadata?: string;
|
|
39
|
+
|
|
40
|
+
/** Full EVM output including generated sources, source maps, etc. */
|
|
41
|
+
evm?: {
|
|
42
|
+
bytecode: {
|
|
43
|
+
object: string;
|
|
44
|
+
opcodes: string;
|
|
45
|
+
sourceMap: string;
|
|
46
|
+
linkReferences: LinkReferences;
|
|
47
|
+
generatedSources?: any[];
|
|
48
|
+
functionDebugData?: Record<string, any>;
|
|
49
|
+
};
|
|
50
|
+
deployedBytecode: {
|
|
51
|
+
object: string;
|
|
52
|
+
opcodes: string;
|
|
53
|
+
sourceMap: string;
|
|
54
|
+
linkReferences: LinkReferences;
|
|
55
|
+
immutableReferences?: Record<
|
|
56
|
+
string,
|
|
57
|
+
Array<{start: number; length: number}>
|
|
58
|
+
>;
|
|
59
|
+
generatedSources?: any[];
|
|
60
|
+
functionDebugData?: Record<string, any>;
|
|
61
|
+
};
|
|
62
|
+
methodIdentifiers?: Record<string, string>;
|
|
63
|
+
gasEstimates?: any;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Developer documentation */
|
|
67
|
+
devdoc?: any;
|
|
68
|
+
|
|
69
|
+
/** User documentation */
|
|
70
|
+
userdoc?: any;
|
|
71
|
+
|
|
72
|
+
/** Storage layout */
|
|
73
|
+
storageLayout?: any;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface LinkReferences {
|
|
77
|
+
[sourceName: string]: {
|
|
78
|
+
[libraryName: string]: Array<{start: number; length: number}>;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Type guard to check if an artifact is a rich artifact
|
|
84
|
+
*/
|
|
85
|
+
export function isRichArtifact(
|
|
86
|
+
artifact: ExternalArtifact,
|
|
87
|
+
): artifact is RichArtifact {
|
|
88
|
+
return 'solcInput' in artifact && artifact.solcInput !== undefined;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Function type for dynamically resolving artifacts
|
|
93
|
+
*/
|
|
94
|
+
export type ArtifactResolver = () =>
|
|
95
|
+
| Promise<ExternalArtifact[]>
|
|
96
|
+
| ExternalArtifact[];
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Configuration for external artifacts
|
|
100
|
+
*/
|
|
101
|
+
export interface ExternalArtifactsConfig {
|
|
102
|
+
/**
|
|
103
|
+
* Paths to artifact files or directories
|
|
104
|
+
* - File path: loads single artifact JSON
|
|
105
|
+
* - Directory path: loads all .json files recursively
|
|
106
|
+
*/
|
|
107
|
+
paths?: string[];
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Function that resolves and returns artifacts dynamically
|
|
111
|
+
* Useful for loading from APIs, databases, or complex logic
|
|
112
|
+
*/
|
|
113
|
+
resolver?: ArtifactResolver;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Solc version to use when creating synthetic compilations
|
|
117
|
+
* @default "0.8.20"
|
|
118
|
+
*/
|
|
119
|
+
solcVersion?: string;
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Whether to log warnings for malformed artifacts
|
|
123
|
+
* @default true
|
|
124
|
+
*/
|
|
125
|
+
warnOnInvalidArtifacts?: boolean;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Enable debug logging to diagnose issues
|
|
129
|
+
* @default false
|
|
130
|
+
*/
|
|
131
|
+
debug?: boolean;
|
|
132
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type {ConfigHooks} from 'hardhat/types/hooks';
|
|
2
|
+
import type {ExternalArtifactsConfig} from '../artifacts/types.js';
|
|
3
|
+
|
|
4
|
+
export default async (): Promise<Partial<ConfigHooks>> => {
|
|
5
|
+
const handlers: Partial<ConfigHooks> = {
|
|
6
|
+
resolveUserConfig: async (
|
|
7
|
+
userConfig,
|
|
8
|
+
resolveConfigurationVariable,
|
|
9
|
+
next,
|
|
10
|
+
) => {
|
|
11
|
+
const resolvedConfig = await next(
|
|
12
|
+
userConfig,
|
|
13
|
+
resolveConfigurationVariable,
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
// Get user's external artifacts config
|
|
17
|
+
const externalArtifactsUserConfig = userConfig.externalArtifacts as
|
|
18
|
+
| ExternalArtifactsConfig
|
|
19
|
+
| undefined;
|
|
20
|
+
|
|
21
|
+
// Apply defaults
|
|
22
|
+
const externalArtifacts: Required<
|
|
23
|
+
Omit<ExternalArtifactsConfig, 'resolver'>
|
|
24
|
+
> & {
|
|
25
|
+
resolver?: ExternalArtifactsConfig['resolver'];
|
|
26
|
+
} = {
|
|
27
|
+
paths: externalArtifactsUserConfig?.paths ?? [],
|
|
28
|
+
resolver: externalArtifactsUserConfig?.resolver,
|
|
29
|
+
solcVersion: externalArtifactsUserConfig?.solcVersion ?? '0.8.20',
|
|
30
|
+
warnOnInvalidArtifacts:
|
|
31
|
+
externalArtifactsUserConfig?.warnOnInvalidArtifacts ?? true,
|
|
32
|
+
debug: externalArtifactsUserConfig?.debug ?? false,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
...resolvedConfig,
|
|
37
|
+
externalArtifacts,
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return handlers;
|
|
43
|
+
};
|