slotdiff 0.1.0-alpha.1 → 0.2.0-alpha.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/CHANGELOG.md +16 -9
- package/README.md +196 -105
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +22 -0
- package/dist/bin.js.map +1 -0
- package/dist/build-info.d.ts +45 -0
- package/dist/build-info.d.ts.map +1 -0
- package/dist/build-info.js +319 -0
- package/dist/build-info.js.map +1 -0
- package/dist/cli.d.ts +25 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +363 -0
- package/dist/cli.js.map +1 -0
- package/dist/erc7201.d.ts +72 -0
- package/dist/erc7201.d.ts.map +1 -0
- package/dist/erc7201.js +340 -0
- package/dist/erc7201.js.map +1 -0
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +8 -0
- package/dist/errors.js.map +1 -1
- package/dist/explorer.js +2 -2
- package/dist/explorer.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/reconstruct.d.ts +51 -3
- package/dist/reconstruct.d.ts.map +1 -1
- package/dist/reconstruct.js +37 -8
- package/dist/reconstruct.js.map +1 -1
- package/dist/report.d.ts +26 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +109 -0
- package/dist/report.js.map +1 -0
- package/dist/resolve.d.ts +11 -0
- package/dist/resolve.d.ts.map +1 -1
- package/dist/resolve.js +11 -1
- package/dist/resolve.js.map +1 -1
- package/package.json +6 -3
- package/src/bin.ts +25 -0
- package/src/build-info.ts +405 -0
- package/src/cli.ts +423 -0
- package/src/erc7201.ts +412 -0
- package/src/errors.ts +9 -0
- package/src/explorer.ts +2 -2
- package/src/index.ts +18 -1
- package/src/reconstruct.ts +111 -10
- package/src/report.ts +155 -0
- package/src/resolve.ts +17 -2
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads a project's own build info: Foundry, Hardhat 2 and Hardhat 3.
|
|
3
|
+
*
|
|
4
|
+
* The build is chosen by content, never by modification time or file count.
|
|
5
|
+
* Foundry keeps every build-info file it ever wrote, so after a few edits
|
|
6
|
+
* several hold the target, most of them describing older sources. Only a build
|
|
7
|
+
* whose sources still match the files on disk is used.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { SolcInput } from "@openzeppelin/upgrades-core";
|
|
11
|
+
import { readFile, readdir, stat } from "node:fs/promises";
|
|
12
|
+
import { isAbsolute, join, relative, resolve } from "node:path";
|
|
13
|
+
import type { ImmutableReferences } from "./bytecode.js";
|
|
14
|
+
|
|
15
|
+
export interface BuildInfoOptions {
|
|
16
|
+
/**
|
|
17
|
+
* One build-info file to use as is, relative to the project root. Its
|
|
18
|
+
* sources are not compared with the files on disk, so it may come from
|
|
19
|
+
* another checkout, such as a CI artifact.
|
|
20
|
+
*/
|
|
21
|
+
file?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface BuildInfoContract {
|
|
25
|
+
/** The build-info file it was read from. */
|
|
26
|
+
file: string;
|
|
27
|
+
/**
|
|
28
|
+
* Fully qualified name as solc saw it, `source.sol:Name`. Hardhat 3 prefixes
|
|
29
|
+
* project sources with `project/`.
|
|
30
|
+
*/
|
|
31
|
+
contract: string;
|
|
32
|
+
/** As recorded: a long version, or a release version for Foundry. Both work with `getSolc`. */
|
|
33
|
+
solcVersion: string;
|
|
34
|
+
/**
|
|
35
|
+
* The compiler input, reduced to the target and what it imports, so that a
|
|
36
|
+
* layout compiles only those sources rather than the whole project.
|
|
37
|
+
*/
|
|
38
|
+
input: SolcInput;
|
|
39
|
+
/** Absent when the build info holds no compiler output. */
|
|
40
|
+
deployedBytecode?: string;
|
|
41
|
+
immutableReferences?: ImmutableReferences;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Foundry, then Hardhat.
|
|
45
|
+
const DIRS = ["out/build-info", "artifacts/build-info"];
|
|
46
|
+
|
|
47
|
+
const NO_INPUT_HINT =
|
|
48
|
+
" Foundry writes the compiler input only with `build_info = true` in foundry.toml or " +
|
|
49
|
+
"`forge build --build-info`.";
|
|
50
|
+
|
|
51
|
+
interface RawBuildInfo {
|
|
52
|
+
_format?: string;
|
|
53
|
+
solcLongVersion?: string;
|
|
54
|
+
input?: SolcInput & { sources?: Record<string, { content?: string }> };
|
|
55
|
+
output?: {
|
|
56
|
+
contracts?: Record<string, Record<string, CompiledContract>>;
|
|
57
|
+
sources?: Record<
|
|
58
|
+
string,
|
|
59
|
+
{ ast?: { nodes?: Array<{ nodeType?: string; absolutePath?: string }> } }
|
|
60
|
+
>;
|
|
61
|
+
};
|
|
62
|
+
userSourceNameMap?: Record<string, string>;
|
|
63
|
+
source_id_to_path?: unknown;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface CompiledContract {
|
|
67
|
+
evm?: { deployedBytecode?: { object?: string; immutableReferences?: ImmutableReferences } };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface Candidate {
|
|
71
|
+
file: string;
|
|
72
|
+
info: RawBuildInfo;
|
|
73
|
+
contract: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Finds `contract` in the build info under `root` and returns what a layout
|
|
78
|
+
* or a bytecode proof needs. `contract` is `path/File.sol:Name`, with the path
|
|
79
|
+
* as written in the project (Hardhat 3's `project/` prefix is optional), or a
|
|
80
|
+
* bare `Name` when only one contract has it.
|
|
81
|
+
*/
|
|
82
|
+
export async function findBuildInfo(
|
|
83
|
+
root: string,
|
|
84
|
+
contract: string,
|
|
85
|
+
options: BuildInfoOptions = {},
|
|
86
|
+
): Promise<BuildInfoContract> {
|
|
87
|
+
const target = parseTarget(contract);
|
|
88
|
+
if (target.path !== undefined && isAbsolute(target.path)) {
|
|
89
|
+
target.path = relative(root, target.path);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (options.file !== undefined) {
|
|
93
|
+
const file = resolve(root, options.file);
|
|
94
|
+
let text: string;
|
|
95
|
+
try {
|
|
96
|
+
text = await readFile(file, "utf8");
|
|
97
|
+
} catch (e) {
|
|
98
|
+
throw new Error(`Cannot read ${options.file} (${(e as NodeJS.ErrnoException).code}).`, {
|
|
99
|
+
cause: e,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
const info = await readBuildInfo(file, text);
|
|
103
|
+
if (info === undefined)
|
|
104
|
+
throw new Error(`${options.file} holds no compiler input.${NO_INPUT_HINT}`);
|
|
105
|
+
const matches = findContract(info, target);
|
|
106
|
+
if (matches.length === 0) throw new Error(`${options.file} has no contract ${contract}.`);
|
|
107
|
+
if (matches.length > 1) throw ambiguousName(contract, matches);
|
|
108
|
+
return toResult({ file, info, contract: matches[0] });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const files = await listBuildInfoFiles(root);
|
|
112
|
+
if (files.length === 0) {
|
|
113
|
+
const isDir = await stat(root).then(
|
|
114
|
+
(st) => st.isDirectory(),
|
|
115
|
+
() => false,
|
|
116
|
+
);
|
|
117
|
+
if (!isDir) throw new Error(`The project root ${root} is not a directory.`);
|
|
118
|
+
throw new Error(
|
|
119
|
+
`No build info found in ${DIRS.join(" or ")} under ${root}. Run from the project root ` +
|
|
120
|
+
`(or pass its path as the root), after a build.${NO_INPUT_HINT}`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const holding: Candidate[] = [];
|
|
125
|
+
let withoutInput = false;
|
|
126
|
+
for (const file of files) {
|
|
127
|
+
// Cheap text check first: build-info files can be large, and most do not hold the target.
|
|
128
|
+
const text = await readFile(file, "utf8");
|
|
129
|
+
if (target.path !== undefined && !text.includes(`${target.path}"`)) continue;
|
|
130
|
+
const info = await readBuildInfo(file, text);
|
|
131
|
+
if (info === undefined) {
|
|
132
|
+
withoutInput = true;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
for (const name of findContract(info, target)) holding.push({ file, info, contract: name });
|
|
136
|
+
}
|
|
137
|
+
if (holding.length === 0) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`No build info holds ${contract}. ` +
|
|
140
|
+
(withoutInput
|
|
141
|
+
? `Some hold no compiler input.${NO_INPUT_HINT}`
|
|
142
|
+
: "Check the name (it is case-sensitive) and give the source path as the compiler " +
|
|
143
|
+
"saw it (for a remapped import, the path under lib/), or just the contract name. " +
|
|
144
|
+
"Or build first."),
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// A build is fresh when the target's file exists and nothing it imports has changed.
|
|
149
|
+
const fresh: Candidate[] = [];
|
|
150
|
+
const stale: Array<{ candidate: Candidate; target: string | undefined }> = [];
|
|
151
|
+
for (const candidate of holding) {
|
|
152
|
+
const disk = await checkDisk(root, candidate);
|
|
153
|
+
if (disk === "gone") continue;
|
|
154
|
+
if (disk.fresh) fresh.push(candidate);
|
|
155
|
+
else stale.push({ candidate, target: disk.target });
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// A bare name is ambiguous across every build that holds it, not only the
|
|
159
|
+
// fresh ones: an incremental build holds just the files it recompiled, so the
|
|
160
|
+
// freshest build can miss a second definition. A stale definition counts only
|
|
161
|
+
// while its file still declares the name (it may have been renamed since).
|
|
162
|
+
const name = target.name.replace(/\$/g, "\\$");
|
|
163
|
+
const declares = new RegExp(`\\b(?:contract|interface|library)\\s+${name}(?![\\w$])`);
|
|
164
|
+
const present = [
|
|
165
|
+
...fresh,
|
|
166
|
+
...stale
|
|
167
|
+
.filter(({ target }) => target === undefined || declares.test(target))
|
|
168
|
+
.map(({ candidate }) => candidate),
|
|
169
|
+
];
|
|
170
|
+
const names = [...new Set(present.map((c) => c.contract))];
|
|
171
|
+
if (names.length > 1) throw ambiguousName(contract, names);
|
|
172
|
+
|
|
173
|
+
if (fresh.length === 0) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
stale.length > 0
|
|
176
|
+
? `Every build info holding ${contract} is older than the sources on disk. Build again.`
|
|
177
|
+
: `${contract} is no longer on disk (moved or deleted). Build again.`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Several fresh builds of the same sources are interchangeable only when the
|
|
182
|
+
// compiler and its settings agree (Foundry profiles can differ in both).
|
|
183
|
+
const byBuild = new Map<string, Candidate>();
|
|
184
|
+
for (const candidate of fresh.sort((a, b) => a.file.localeCompare(b.file))) {
|
|
185
|
+
const key = buildKey(candidate.info);
|
|
186
|
+
if (!byBuild.has(key)) byBuild.set(key, candidate);
|
|
187
|
+
}
|
|
188
|
+
if (byBuild.size > 1) {
|
|
189
|
+
const files = [...byBuild.values()].map((c) => relative(root, c.file));
|
|
190
|
+
throw new Error(
|
|
191
|
+
`${contract} was built with different compilers or settings in ${files.join(", ")} ` +
|
|
192
|
+
`(a project with several solc versions compiles shared files once per version). ` +
|
|
193
|
+
`Pick one with the \`file\` option (\`--build-info\` on the command line); ` +
|
|
194
|
+
`\`forge clean\` drops leftover builds.`,
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
return toResult([...byBuild.values()][0]);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
interface Target {
|
|
201
|
+
/** Source path as given, without Hardhat 3's `project/` prefix. */
|
|
202
|
+
path?: string;
|
|
203
|
+
name: string;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function parseTarget(contract: string): Target {
|
|
207
|
+
const colon = contract.lastIndexOf(":");
|
|
208
|
+
const name = contract.slice(colon + 1);
|
|
209
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(name)) {
|
|
210
|
+
// Not quoted: a URL passed here by mistake can carry an API key.
|
|
211
|
+
throw new Error("Expected a contract name or `path/File.sol:Name`.");
|
|
212
|
+
}
|
|
213
|
+
if (colon === -1) return { name };
|
|
214
|
+
const path = contract.slice(0, colon).replace(/^\.\//, "");
|
|
215
|
+
return { path: path.replace(/^project\//, ""), name };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function listBuildInfoFiles(root: string): Promise<string[]> {
|
|
219
|
+
const files: string[] = [];
|
|
220
|
+
for (const dir of DIRS) {
|
|
221
|
+
let entries: string[];
|
|
222
|
+
try {
|
|
223
|
+
entries = await readdir(resolve(root, dir));
|
|
224
|
+
} catch (e) {
|
|
225
|
+
if ((e as NodeJS.ErrnoException).code === "ENOENT") continue;
|
|
226
|
+
throw e;
|
|
227
|
+
}
|
|
228
|
+
for (const entry of entries.sort()) {
|
|
229
|
+
// Hardhat 3 keeps the compiler output next to the build info, in `<id>.output.json`.
|
|
230
|
+
if (entry.endsWith(".json") && !entry.endsWith(".output.json")) {
|
|
231
|
+
files.push(join(resolve(root, dir), entry));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return files;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Undefined for a file with no compiler input: Foundry writes one per build
|
|
240
|
+
* (only `id` and `source_id_to_path`) unless asked for full build info.
|
|
241
|
+
*/
|
|
242
|
+
async function readBuildInfo(file: string, text: string): Promise<RawBuildInfo | undefined> {
|
|
243
|
+
let info: RawBuildInfo;
|
|
244
|
+
try {
|
|
245
|
+
info = JSON.parse(text) as RawBuildInfo;
|
|
246
|
+
} catch (e) {
|
|
247
|
+
throw new Error(`${file} is not JSON: ${(e as Error).message}`, { cause: e });
|
|
248
|
+
}
|
|
249
|
+
if (info?.input === undefined && info?.source_id_to_path !== undefined) return undefined;
|
|
250
|
+
if (
|
|
251
|
+
typeof info?.solcLongVersion !== "string" ||
|
|
252
|
+
typeof info.input?.sources !== "object" ||
|
|
253
|
+
info.input.sources === null
|
|
254
|
+
) {
|
|
255
|
+
throw new Error(`${file} is not a build-info file (no solcLongVersion or input sources).`);
|
|
256
|
+
}
|
|
257
|
+
if (info.output === undefined) {
|
|
258
|
+
const outputFile = file.replace(/\.json$/, ".output.json");
|
|
259
|
+
try {
|
|
260
|
+
info.output = (JSON.parse(await readFile(outputFile, "utf8")) as RawBuildInfo).output;
|
|
261
|
+
} catch (e) {
|
|
262
|
+
if ((e as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
263
|
+
throw new Error(`Cannot read ${outputFile}: ${(e as Error).message}`, { cause: e });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return info;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Fully qualified names in `info` that match the target.
|
|
271
|
+
function findContract(info: RawBuildInfo, target: Target): string[] {
|
|
272
|
+
const sources = Object.keys(info.input!.sources);
|
|
273
|
+
if (target.path !== undefined) {
|
|
274
|
+
const path = target.path;
|
|
275
|
+
const source = sources.find((s) => s === path || s === `project/${path}`);
|
|
276
|
+
if (source === undefined) return [];
|
|
277
|
+
const compiled = info.output?.contracts?.[source];
|
|
278
|
+
// Without output the name cannot be checked here; solc checks it when the layout is built.
|
|
279
|
+
if (compiled !== undefined && !Object.hasOwn(compiled, target.name)) return [];
|
|
280
|
+
return [`${source}:${target.name}`];
|
|
281
|
+
}
|
|
282
|
+
if (info.output?.contracts === undefined) return [];
|
|
283
|
+
return Object.entries(info.output.contracts)
|
|
284
|
+
.filter(([, contracts]) => Object.hasOwn(contracts, target.name))
|
|
285
|
+
.map(([source]) => `${source}:${target.name}`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Compares the target and every source it imports, directly or not, with the
|
|
290
|
+
* disk. A changed base contract changes the target's layout; a changed
|
|
291
|
+
* unrelated file does not, and must not make a build stale: an incremental
|
|
292
|
+
* build holds only the files it recompiled, so the build that holds the target
|
|
293
|
+
* is often older than files the target never imports. `gone` when the target's
|
|
294
|
+
* file no longer exists; a dependency not found on disk (hoisted node_modules,
|
|
295
|
+
* say) is not checked. `target` is the target's text on disk, when found.
|
|
296
|
+
*/
|
|
297
|
+
async function checkDisk(
|
|
298
|
+
root: string,
|
|
299
|
+
{ info, contract }: Candidate,
|
|
300
|
+
): Promise<"gone" | { fresh: boolean; target: string | undefined }> {
|
|
301
|
+
const targetSource = sourceOf(contract);
|
|
302
|
+
let target: string | undefined;
|
|
303
|
+
let fresh = true;
|
|
304
|
+
// The target comes first.
|
|
305
|
+
for (const source of dependencies(info, targetSource)) {
|
|
306
|
+
const onDisk = await readSource(root, info, source);
|
|
307
|
+
if (source === targetSource) {
|
|
308
|
+
if (onDisk === null) return "gone";
|
|
309
|
+
target = onDisk;
|
|
310
|
+
}
|
|
311
|
+
const content = info.input!.sources[source]?.content;
|
|
312
|
+
if (typeof onDisk === "string" && typeof content === "string" && onDisk !== content) {
|
|
313
|
+
fresh = false;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return { fresh, target };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* `source` and everything it imports, from the import directives in the
|
|
321
|
+
* output's ASTs. Without them, every source in the build (the cautious answer).
|
|
322
|
+
*/
|
|
323
|
+
function dependencies(info: RawBuildInfo, source: string): string[] {
|
|
324
|
+
// The target first, as checkDisk expects.
|
|
325
|
+
const all = [source, ...Object.keys(info.input!.sources).filter((s) => s !== source)];
|
|
326
|
+
const asts = info.output?.sources;
|
|
327
|
+
const seen = new Set([source]);
|
|
328
|
+
const queue = [source];
|
|
329
|
+
while (queue.length > 0) {
|
|
330
|
+
const nodes = asts?.[queue.pop()!]?.ast?.nodes;
|
|
331
|
+
if (nodes === undefined) return all;
|
|
332
|
+
for (const node of nodes) {
|
|
333
|
+
const imported = node.nodeType === "ImportDirective" ? node.absolutePath : undefined;
|
|
334
|
+
if (imported !== undefined && !seen.has(imported)) {
|
|
335
|
+
seen.add(imported);
|
|
336
|
+
queue.push(imported);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return [...seen];
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* The file on disk behind a build's source name: undefined when it cannot be
|
|
345
|
+
* located by name (Hardhat 3's versioned `npm/` paths), null when it should be
|
|
346
|
+
* there and is not.
|
|
347
|
+
*/
|
|
348
|
+
async function readSource(
|
|
349
|
+
root: string,
|
|
350
|
+
info: RawBuildInfo,
|
|
351
|
+
source: string,
|
|
352
|
+
): Promise<string | null | undefined> {
|
|
353
|
+
const user = Object.entries(info.userSourceNameMap ?? {}).find(([, s]) => s === source)?.[0];
|
|
354
|
+
if (user === undefined && source.startsWith("npm/")) return undefined;
|
|
355
|
+
const names =
|
|
356
|
+
user !== undefined
|
|
357
|
+
? [user]
|
|
358
|
+
: [source, ...(source.startsWith("project/") ? [source.slice("project/".length)] : [])];
|
|
359
|
+
const paths = names.flatMap((name) => [resolve(root, name), resolve(root, "node_modules", name)]);
|
|
360
|
+
return (await readFirst(paths)) ?? null;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async function readFirst(paths: string[]): Promise<string | undefined> {
|
|
364
|
+
for (const path of paths) {
|
|
365
|
+
try {
|
|
366
|
+
return await readFile(path, "utf8");
|
|
367
|
+
} catch (e) {
|
|
368
|
+
const code = (e as NodeJS.ErrnoException).code;
|
|
369
|
+
if (code !== "ENOENT" && code !== "ENOTDIR") throw e;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function buildKey(info: RawBuildInfo): string {
|
|
376
|
+
const settings = { ...(info.input!.settings as Record<string, unknown> | undefined) };
|
|
377
|
+
delete settings.outputSelection;
|
|
378
|
+
return JSON.stringify([info.solcLongVersion, settings]);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function ambiguousName(contract: string, names: string[]): Error {
|
|
382
|
+
return new Error(`${contract} is ambiguous: ${names.join(", ")}. Use \`path/File.sol:Name\`.`);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function sourceOf(contract: string): string {
|
|
386
|
+
return contract.slice(0, contract.lastIndexOf(":"));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function toResult({ file, info, contract }: Candidate): BuildInfoContract {
|
|
390
|
+
const source = sourceOf(contract);
|
|
391
|
+
const deployed =
|
|
392
|
+
info.output?.contracts?.[source]?.[contract.slice(source.length + 1)]?.evm?.deployedBytecode;
|
|
393
|
+
const sources = info.input!.sources;
|
|
394
|
+
return {
|
|
395
|
+
file,
|
|
396
|
+
contract,
|
|
397
|
+
solcVersion: info.solcLongVersion!,
|
|
398
|
+
input: {
|
|
399
|
+
...info.input!,
|
|
400
|
+
sources: Object.fromEntries(dependencies(info, source).map((s) => [s, sources[s]])),
|
|
401
|
+
},
|
|
402
|
+
deployedBytecode: deployed?.object,
|
|
403
|
+
immutableReferences: deployed?.immutableReferences,
|
|
404
|
+
};
|
|
405
|
+
}
|