forge-pack 0.1.0
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/dist/cli-Dc4X1WbF.d.ts +1 -0
- package/dist/cli.js +83 -0
- package/dist/index-ECFAYGrl.d.ts +68 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.js +3 -0
- package/dist/pack-A6vKuf56.js +331 -0
- package/package.json +41 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { i as resolveLibraries, n as generateDeployer, r as parseArtifact, t as findArtifact } from "./pack-A6vKuf56.js";
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
import { execSync } from "node:child_process";
|
|
5
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { join, resolve } from "node:path";
|
|
7
|
+
|
|
8
|
+
//#region src/cli.ts
|
|
9
|
+
const { values, positionals } = parseArgs({
|
|
10
|
+
allowPositionals: true,
|
|
11
|
+
options: {
|
|
12
|
+
out: {
|
|
13
|
+
type: "string",
|
|
14
|
+
default: "./out"
|
|
15
|
+
},
|
|
16
|
+
output: {
|
|
17
|
+
type: "string",
|
|
18
|
+
default: "./deployers"
|
|
19
|
+
},
|
|
20
|
+
build: {
|
|
21
|
+
type: "boolean",
|
|
22
|
+
default: false
|
|
23
|
+
},
|
|
24
|
+
solc: { type: "string" },
|
|
25
|
+
pragma: {
|
|
26
|
+
type: "string",
|
|
27
|
+
default: ">=0.8.0"
|
|
28
|
+
},
|
|
29
|
+
help: {
|
|
30
|
+
type: "boolean",
|
|
31
|
+
short: "h",
|
|
32
|
+
default: false
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
if (values.help || positionals.length === 0) {
|
|
37
|
+
console.log(`Usage: forge-pack <ContractName> [options]
|
|
38
|
+
|
|
39
|
+
Options:
|
|
40
|
+
--out <dir> Forge output directory (default: ./out)
|
|
41
|
+
--output <dir> Where to write the deployer .sol file (default: ./deployers)
|
|
42
|
+
--build Run \`forge build\` before reading artifacts
|
|
43
|
+
--solc <version> Filter artifact by solc version (when multiple exist)
|
|
44
|
+
--pragma <range> Solidity pragma for generated file (default: >=0.8.0)
|
|
45
|
+
-h, --help Show this help message`);
|
|
46
|
+
process.exit(values.help ? 0 : 1);
|
|
47
|
+
}
|
|
48
|
+
const contractName = positionals[0];
|
|
49
|
+
const outDir = resolve(values.out);
|
|
50
|
+
const outputDir = resolve(values.output);
|
|
51
|
+
const pragma = values.pragma;
|
|
52
|
+
if (values.build) {
|
|
53
|
+
console.log("Running forge build...");
|
|
54
|
+
execSync("forge build", { stdio: "inherit" });
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const parsed = parseArtifact(findArtifact(contractName, outDir, { solcVersion: values.solc }), contractName);
|
|
58
|
+
if (!parsed.bytecode) {
|
|
59
|
+
console.error(`Error: No bytecode found for "${contractName}". Is it an abstract contract or interface?`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
const hasLinks = Object.keys(parsed.linkReferences).length > 0;
|
|
63
|
+
let libraries;
|
|
64
|
+
if (hasLinks) {
|
|
65
|
+
libraries = resolveLibraries(parsed.linkReferences, outDir);
|
|
66
|
+
const libNames = libraries.map((l) => l.lib);
|
|
67
|
+
console.log(`Resolved ${libNames.length} library dep(s): ${libNames.join(", ")}`);
|
|
68
|
+
}
|
|
69
|
+
const solidity = generateDeployer(parsed, {
|
|
70
|
+
pragma,
|
|
71
|
+
libraries
|
|
72
|
+
});
|
|
73
|
+
mkdirSync(outputDir, { recursive: true });
|
|
74
|
+
const outPath = join(outputDir, `${contractName}Deployer.sol`);
|
|
75
|
+
writeFileSync(outPath, solidity);
|
|
76
|
+
console.log(`Generated ${outPath}`);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
console.error(`Error: ${err.message}`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
//#endregion
|
|
83
|
+
export { };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
//#region src/pack.d.ts
|
|
2
|
+
interface LinkReference {
|
|
3
|
+
start: number;
|
|
4
|
+
length: number;
|
|
5
|
+
}
|
|
6
|
+
interface LinkReferences {
|
|
7
|
+
[file: string]: {
|
|
8
|
+
[lib: string]: LinkReference[];
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
interface AbiParam {
|
|
12
|
+
name: string;
|
|
13
|
+
type: string;
|
|
14
|
+
internalType?: string;
|
|
15
|
+
components?: AbiParam[];
|
|
16
|
+
indexed?: boolean;
|
|
17
|
+
}
|
|
18
|
+
interface AbiEntry {
|
|
19
|
+
type: string;
|
|
20
|
+
name?: string;
|
|
21
|
+
inputs?: AbiParam[];
|
|
22
|
+
outputs?: AbiParam[];
|
|
23
|
+
stateMutability?: string;
|
|
24
|
+
}
|
|
25
|
+
interface ParsedArtifact {
|
|
26
|
+
contractName: string;
|
|
27
|
+
abi: AbiEntry[];
|
|
28
|
+
bytecode: string;
|
|
29
|
+
linkReferences: LinkReferences;
|
|
30
|
+
sourcePath?: string;
|
|
31
|
+
solcVersion?: string;
|
|
32
|
+
optimizerRuns?: number;
|
|
33
|
+
viaIR?: boolean;
|
|
34
|
+
evmVersion?: string;
|
|
35
|
+
}
|
|
36
|
+
interface FindArtifactOptions {
|
|
37
|
+
solcVersion?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A library dependency resolved from linkReferences, with its own parsed
|
|
41
|
+
* artifact and the param name used in generated code.
|
|
42
|
+
*/
|
|
43
|
+
interface ResolvedLibrary {
|
|
44
|
+
/** Parameter-style name, e.g. "mathLib" */
|
|
45
|
+
paramName: string;
|
|
46
|
+
/** Source file declaring the library, e.g. "src/libs/MathLib.sol" */
|
|
47
|
+
file: string;
|
|
48
|
+
/** Library contract name, e.g. "MathLib" */
|
|
49
|
+
lib: string;
|
|
50
|
+
/** Parsed artifact for this library */
|
|
51
|
+
artifact: ParsedArtifact;
|
|
52
|
+
/** Param names of libraries this library depends on (its own linkReferences) */
|
|
53
|
+
deps: string[];
|
|
54
|
+
}
|
|
55
|
+
declare function findArtifact(contractName: string, outDir: string, options?: FindArtifactOptions): string;
|
|
56
|
+
declare function parseArtifact(artifactPath: string, contractName: string): ParsedArtifact;
|
|
57
|
+
/**
|
|
58
|
+
* Recursively resolve all library dependencies for a contract's linkReferences.
|
|
59
|
+
* Returns libraries in topological order (deploy-order: leaves first).
|
|
60
|
+
*/
|
|
61
|
+
declare function resolveLibraries(linkRefs: LinkReferences, outDir: string): ResolvedLibrary[];
|
|
62
|
+
interface GenerateDeployerOptions {
|
|
63
|
+
pragma?: string;
|
|
64
|
+
libraries?: ResolvedLibrary[];
|
|
65
|
+
}
|
|
66
|
+
declare function generateDeployer(parsed: ParsedArtifact, pragmaOrOpts?: string | GenerateDeployerOptions): string;
|
|
67
|
+
//#endregion
|
|
68
|
+
export { type AbiEntry, type AbiParam, type FindArtifactOptions, type GenerateDeployerOptions, type LinkReference, type LinkReferences, type ParsedArtifact, type ResolvedLibrary, findArtifact, generateDeployer, parseArtifact, resolveLibraries };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
//#region src/pack.d.ts
|
|
2
|
+
interface LinkReference {
|
|
3
|
+
start: number;
|
|
4
|
+
length: number;
|
|
5
|
+
}
|
|
6
|
+
interface LinkReferences {
|
|
7
|
+
[file: string]: {
|
|
8
|
+
[lib: string]: LinkReference[];
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
interface AbiParam {
|
|
12
|
+
name: string;
|
|
13
|
+
type: string;
|
|
14
|
+
internalType?: string;
|
|
15
|
+
components?: AbiParam[];
|
|
16
|
+
indexed?: boolean;
|
|
17
|
+
}
|
|
18
|
+
interface AbiEntry {
|
|
19
|
+
type: string;
|
|
20
|
+
name?: string;
|
|
21
|
+
inputs?: AbiParam[];
|
|
22
|
+
outputs?: AbiParam[];
|
|
23
|
+
stateMutability?: string;
|
|
24
|
+
}
|
|
25
|
+
interface ParsedArtifact {
|
|
26
|
+
contractName: string;
|
|
27
|
+
abi: AbiEntry[];
|
|
28
|
+
bytecode: string;
|
|
29
|
+
linkReferences: LinkReferences;
|
|
30
|
+
sourcePath?: string;
|
|
31
|
+
solcVersion?: string;
|
|
32
|
+
optimizerRuns?: number;
|
|
33
|
+
viaIR?: boolean;
|
|
34
|
+
evmVersion?: string;
|
|
35
|
+
}
|
|
36
|
+
interface FindArtifactOptions {
|
|
37
|
+
solcVersion?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* A library dependency resolved from linkReferences, with its own parsed
|
|
41
|
+
* artifact and the param name used in generated code.
|
|
42
|
+
*/
|
|
43
|
+
interface ResolvedLibrary {
|
|
44
|
+
/** Parameter-style name, e.g. "mathLib" */
|
|
45
|
+
paramName: string;
|
|
46
|
+
/** Source file declaring the library, e.g. "src/libs/MathLib.sol" */
|
|
47
|
+
file: string;
|
|
48
|
+
/** Library contract name, e.g. "MathLib" */
|
|
49
|
+
lib: string;
|
|
50
|
+
/** Parsed artifact for this library */
|
|
51
|
+
artifact: ParsedArtifact;
|
|
52
|
+
/** Param names of libraries this library depends on (its own linkReferences) */
|
|
53
|
+
deps: string[];
|
|
54
|
+
}
|
|
55
|
+
declare function findArtifact(contractName: string, outDir: string, options?: FindArtifactOptions): string;
|
|
56
|
+
declare function parseArtifact(artifactPath: string, contractName: string): ParsedArtifact;
|
|
57
|
+
/**
|
|
58
|
+
* Recursively resolve all library dependencies for a contract's linkReferences.
|
|
59
|
+
* Returns libraries in topological order (deploy-order: leaves first).
|
|
60
|
+
*/
|
|
61
|
+
declare function resolveLibraries(linkRefs: LinkReferences, outDir: string): ResolvedLibrary[];
|
|
62
|
+
interface GenerateDeployerOptions {
|
|
63
|
+
pragma?: string;
|
|
64
|
+
libraries?: ResolvedLibrary[];
|
|
65
|
+
}
|
|
66
|
+
declare function generateDeployer(parsed: ParsedArtifact, pragmaOrOpts?: string | GenerateDeployerOptions): string;
|
|
67
|
+
//#endregion
|
|
68
|
+
export { type AbiEntry, type AbiParam, type FindArtifactOptions, type GenerateDeployerOptions, type LinkReference, type LinkReferences, type ParsedArtifact, type ResolvedLibrary, findArtifact, generateDeployer, parseArtifact, resolveLibraries };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
//#region src/pack.ts
|
|
5
|
+
function findArtifact(contractName, outDir, options) {
|
|
6
|
+
const candidates = [];
|
|
7
|
+
const entries = readdirSync(outDir);
|
|
8
|
+
for (const entry of entries) {
|
|
9
|
+
const entryPath = join(outDir, entry);
|
|
10
|
+
if (!statSync(entryPath).isDirectory()) continue;
|
|
11
|
+
const jsonPath = join(entryPath, `${contractName}.json`);
|
|
12
|
+
try {
|
|
13
|
+
statSync(jsonPath);
|
|
14
|
+
candidates.push(jsonPath);
|
|
15
|
+
} catch {}
|
|
16
|
+
}
|
|
17
|
+
if (candidates.length === 0) throw new Error(`No artifact found for "${contractName}" in ${outDir}. Run \`forge build\` first.`);
|
|
18
|
+
if (options?.solcVersion && candidates.length > 1) {
|
|
19
|
+
for (const c of candidates) if ((JSON.parse(readFileSync(c, "utf-8"))?.metadata?.compiler?.version)?.startsWith(options.solcVersion)) return c;
|
|
20
|
+
throw new Error(`No artifact for "${contractName}" compiled with solc ${options.solcVersion}.`);
|
|
21
|
+
}
|
|
22
|
+
if (candidates.length > 1) throw new Error(`Multiple artifacts found for "${contractName}". Use --solc to disambiguate:\n` + candidates.map((c) => ` ${c}`).join("\n"));
|
|
23
|
+
return candidates[0];
|
|
24
|
+
}
|
|
25
|
+
function parseArtifact(artifactPath, contractName) {
|
|
26
|
+
const raw = JSON.parse(readFileSync(artifactPath, "utf-8"));
|
|
27
|
+
const abi = raw.abi ?? [];
|
|
28
|
+
let bytecodeHex = raw.bytecode?.object ?? "";
|
|
29
|
+
if (bytecodeHex.startsWith("0x")) bytecodeHex = bytecodeHex.slice(2);
|
|
30
|
+
const linkReferences = raw.bytecode?.linkReferences ?? {};
|
|
31
|
+
const metadata = raw.metadata;
|
|
32
|
+
const solcVersion = metadata?.compiler?.version;
|
|
33
|
+
const settings = metadata?.settings;
|
|
34
|
+
const optimizerRuns = settings?.optimizer?.runs;
|
|
35
|
+
const viaIR = settings?.viaIR;
|
|
36
|
+
const evmVersion = settings?.evmVersion;
|
|
37
|
+
let sourcePath;
|
|
38
|
+
if (metadata?.settings?.compilationTarget) {
|
|
39
|
+
const targets = metadata.settings.compilationTarget;
|
|
40
|
+
sourcePath = Object.keys(targets)[0];
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
contractName,
|
|
44
|
+
abi,
|
|
45
|
+
bytecode: bytecodeHex,
|
|
46
|
+
linkReferences,
|
|
47
|
+
sourcePath,
|
|
48
|
+
solcVersion,
|
|
49
|
+
optimizerRuns,
|
|
50
|
+
viaIR,
|
|
51
|
+
evmVersion
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Collect unique library identities from linkReferences.
|
|
56
|
+
* Returns deduped entries as { file, lib } pairs.
|
|
57
|
+
*/
|
|
58
|
+
function collectLibIds(linkRefs) {
|
|
59
|
+
const seen = /* @__PURE__ */ new Set();
|
|
60
|
+
const result = [];
|
|
61
|
+
for (const [file, libs] of Object.entries(linkRefs)) for (const lib of Object.keys(libs)) {
|
|
62
|
+
const key = `${file}:${lib}`;
|
|
63
|
+
if (!seen.has(key)) {
|
|
64
|
+
seen.add(key);
|
|
65
|
+
result.push({
|
|
66
|
+
file,
|
|
67
|
+
lib
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
function makeParamName(libName) {
|
|
74
|
+
return libName.charAt(0).toLowerCase() + libName.slice(1);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Recursively resolve all library dependencies for a contract's linkReferences.
|
|
78
|
+
* Returns libraries in topological order (deploy-order: leaves first).
|
|
79
|
+
*/
|
|
80
|
+
function resolveLibraries(linkRefs, outDir) {
|
|
81
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
82
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
83
|
+
function resolve(file, lib) {
|
|
84
|
+
const key = `${file}:${lib}`;
|
|
85
|
+
if (resolved.has(key)) return resolved.get(key);
|
|
86
|
+
if (visiting.has(key)) throw new Error(`Circular library dependency detected: ${key}`);
|
|
87
|
+
visiting.add(key);
|
|
88
|
+
const artifact = parseArtifact(findArtifact(lib, outDir), lib);
|
|
89
|
+
const libIds = collectLibIds(artifact.linkReferences);
|
|
90
|
+
const deps = [];
|
|
91
|
+
for (const dep of libIds) {
|
|
92
|
+
const depResolved = resolve(dep.file, dep.lib);
|
|
93
|
+
deps.push(depResolved.paramName);
|
|
94
|
+
}
|
|
95
|
+
visiting.delete(key);
|
|
96
|
+
const entry = {
|
|
97
|
+
paramName: makeParamName(lib),
|
|
98
|
+
file,
|
|
99
|
+
lib,
|
|
100
|
+
artifact,
|
|
101
|
+
deps
|
|
102
|
+
};
|
|
103
|
+
resolved.set(key, entry);
|
|
104
|
+
return entry;
|
|
105
|
+
}
|
|
106
|
+
const topLevel = collectLibIds(linkRefs);
|
|
107
|
+
for (const { file, lib } of topLevel) resolve(file, lib);
|
|
108
|
+
return Array.from(resolved.values());
|
|
109
|
+
}
|
|
110
|
+
function extractStructName(internalType) {
|
|
111
|
+
let name = internalType.replace(/^struct\s+/, "");
|
|
112
|
+
name = name.replace(/(\[\d*\])+$/, "");
|
|
113
|
+
const dotIdx = name.lastIndexOf(".");
|
|
114
|
+
if (dotIdx !== -1) name = name.slice(dotIdx + 1);
|
|
115
|
+
return name;
|
|
116
|
+
}
|
|
117
|
+
function collectStructDefs(params) {
|
|
118
|
+
const seen = /* @__PURE__ */ new Map();
|
|
119
|
+
function walk(param) {
|
|
120
|
+
const it = param.internalType ?? "";
|
|
121
|
+
if (!it.startsWith("struct ") || !param.components) return;
|
|
122
|
+
for (const comp of param.components) walk(comp);
|
|
123
|
+
const name = extractStructName(it);
|
|
124
|
+
if (seen.has(name)) return;
|
|
125
|
+
const fields = param.components.map((comp, i) => ({
|
|
126
|
+
type: abiTypeToSolidity(comp),
|
|
127
|
+
name: comp.name || `field${i}`
|
|
128
|
+
}));
|
|
129
|
+
seen.set(name, {
|
|
130
|
+
name,
|
|
131
|
+
fields
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
for (const p of params) walk(p);
|
|
135
|
+
return Array.from(seen.values());
|
|
136
|
+
}
|
|
137
|
+
function abiTypeToSolidity(param) {
|
|
138
|
+
if (param.internalType) {
|
|
139
|
+
const it = param.internalType;
|
|
140
|
+
if (it.startsWith("contract ")) return "address";
|
|
141
|
+
if (it.startsWith("enum ")) return param.type;
|
|
142
|
+
if (it.startsWith("struct ")) return extractStructName(it) + param.type.replace(/^tuple/, "");
|
|
143
|
+
return it;
|
|
144
|
+
}
|
|
145
|
+
return param.type;
|
|
146
|
+
}
|
|
147
|
+
function formatParamWithStructs(param, index, structNames) {
|
|
148
|
+
const solType = abiTypeToSolidity(param);
|
|
149
|
+
return `${solType}${needsMemoryLocation(solType) || isStructType(solType, structNames) ? " memory" : ""} ${param.name || `arg${index}`}`;
|
|
150
|
+
}
|
|
151
|
+
function needsMemoryLocation(solType) {
|
|
152
|
+
if (solType === "string" || solType === "bytes") return true;
|
|
153
|
+
if (solType.endsWith("[]") || /\[\d+\]$/.test(solType)) return true;
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
function isStructType(solType, structNames) {
|
|
157
|
+
const baseName = solType.replace(/(\[\d*\])+$/, "");
|
|
158
|
+
return structNames.has(baseName);
|
|
159
|
+
}
|
|
160
|
+
function buildBytecodeSegments(bytecode, linkReferences) {
|
|
161
|
+
const placeholders = [];
|
|
162
|
+
for (const [file, libs] of Object.entries(linkReferences)) for (const [lib, refs] of Object.entries(libs)) for (const ref of refs) placeholders.push({
|
|
163
|
+
start: ref.start,
|
|
164
|
+
length: ref.length,
|
|
165
|
+
file,
|
|
166
|
+
lib
|
|
167
|
+
});
|
|
168
|
+
placeholders.sort((a, b) => a.start - b.start);
|
|
169
|
+
if (placeholders.length === 0) return {
|
|
170
|
+
segments: [{
|
|
171
|
+
type: "hex",
|
|
172
|
+
value: bytecode
|
|
173
|
+
}],
|
|
174
|
+
libParams: []
|
|
175
|
+
};
|
|
176
|
+
const libMap = /* @__PURE__ */ new Map();
|
|
177
|
+
const libParams = [];
|
|
178
|
+
for (const p of placeholders) {
|
|
179
|
+
const key = `${p.file}:${p.lib}`;
|
|
180
|
+
if (!libMap.has(key)) {
|
|
181
|
+
const paramName = makeParamName(p.lib);
|
|
182
|
+
libMap.set(key, paramName);
|
|
183
|
+
libParams.push({
|
|
184
|
+
name: paramName,
|
|
185
|
+
file: p.file,
|
|
186
|
+
lib: p.lib
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const segments = [];
|
|
191
|
+
let cursor = 0;
|
|
192
|
+
for (const p of placeholders) {
|
|
193
|
+
const hexStart = p.start * 2;
|
|
194
|
+
const hexEnd = (p.start + p.length) * 2;
|
|
195
|
+
if (hexStart > cursor) segments.push({
|
|
196
|
+
type: "hex",
|
|
197
|
+
value: bytecode.slice(cursor, hexStart)
|
|
198
|
+
});
|
|
199
|
+
const key = `${p.file}:${p.lib}`;
|
|
200
|
+
segments.push({
|
|
201
|
+
type: "lib",
|
|
202
|
+
value: libMap.get(key)
|
|
203
|
+
});
|
|
204
|
+
cursor = hexEnd;
|
|
205
|
+
}
|
|
206
|
+
if (cursor < bytecode.length) segments.push({
|
|
207
|
+
type: "hex",
|
|
208
|
+
value: bytecode.slice(cursor)
|
|
209
|
+
});
|
|
210
|
+
return {
|
|
211
|
+
segments,
|
|
212
|
+
libParams
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
function renderInitcodeBody(bytecode, segments, hasLinks) {
|
|
216
|
+
if (!hasLinks) return ` return hex"${bytecode}";`;
|
|
217
|
+
return ` return abi.encodePacked(${segments.map((seg) => seg.type === "hex" ? `hex"${seg.value}"` : seg.value).join(", ")});`;
|
|
218
|
+
}
|
|
219
|
+
function generateDeployer(parsed, pragmaOrOpts) {
|
|
220
|
+
const opts = typeof pragmaOrOpts === "string" ? { pragma: pragmaOrOpts } : pragmaOrOpts ?? {};
|
|
221
|
+
const pragma = opts.pragma ?? ">=0.8.0";
|
|
222
|
+
const resolvedLibs = opts.libraries ?? [];
|
|
223
|
+
const { contractName, abi, bytecode, linkReferences } = parsed;
|
|
224
|
+
const libName = `${contractName}Deployer`;
|
|
225
|
+
const ctorParams = abi.find((e) => e.type === "constructor")?.inputs ?? [];
|
|
226
|
+
const structDefs = collectStructDefs(ctorParams);
|
|
227
|
+
const structNames = new Set(structDefs.map((s) => s.name));
|
|
228
|
+
const { segments: mainSegments, libParams: mainLibParams } = buildBytecodeSegments(bytecode, linkReferences);
|
|
229
|
+
const hasLinks = mainLibParams.length > 0;
|
|
230
|
+
const inlineLibs = hasLinks && resolvedLibs.length > 0;
|
|
231
|
+
const metaLines = [];
|
|
232
|
+
if (parsed.sourcePath) metaLines.push(`@notice Source Contract: ${parsed.sourcePath}`);
|
|
233
|
+
if (parsed.solcVersion) metaLines.push(`- solc: ${parsed.solcVersion}`);
|
|
234
|
+
if (parsed.optimizerRuns !== void 0) metaLines.push(`- optimizer_runs: ${parsed.optimizerRuns}`);
|
|
235
|
+
if (parsed.viaIR) metaLines.push(`- viaIR: true`);
|
|
236
|
+
if (parsed.evmVersion) metaLines.push(`- evm_version: ${parsed.evmVersion}`);
|
|
237
|
+
const metaBlock = metaLines.length > 0 ? [
|
|
238
|
+
` /**`,
|
|
239
|
+
` * @dev autogenerated by forge-pack`,
|
|
240
|
+
` *`,
|
|
241
|
+
...metaLines.map((l) => ` * ${l}`),
|
|
242
|
+
` */`
|
|
243
|
+
].join("\n") + "\n" : "";
|
|
244
|
+
const structBlock = structDefs.map((s) => {
|
|
245
|
+
const fields = s.fields.map((f) => ` ${f.type} ${f.name};`).join("\n");
|
|
246
|
+
return ` struct ${s.name} {\n${fields}\n }`;
|
|
247
|
+
}).join("\n\n");
|
|
248
|
+
const initcodeParams = hasLinks ? mainLibParams.map((lp) => `address ${lp.name}`).join(", ") : "";
|
|
249
|
+
const initcodeBody = renderInitcodeBody(bytecode, mainSegments, hasLinks);
|
|
250
|
+
const libInitcodeFns = [];
|
|
251
|
+
if (inlineLibs) for (const rlib of resolvedLibs) {
|
|
252
|
+
const { segments: libSegs, libParams: libLibParams } = buildBytecodeSegments(rlib.artifact.bytecode, rlib.artifact.linkReferences);
|
|
253
|
+
const libHasLinks = libLibParams.length > 0;
|
|
254
|
+
const fnParams = libHasLinks ? libLibParams.map((lp) => `address ${lp.name}`).join(", ") : "";
|
|
255
|
+
const fnBody = renderInitcodeBody(rlib.artifact.bytecode, libSegs, libHasLinks);
|
|
256
|
+
libInitcodeFns.push(` function _${rlib.paramName}Initcode(${fnParams}) private pure returns (bytes memory) {\n${fnBody}\n }`);
|
|
257
|
+
}
|
|
258
|
+
const deployParams = [];
|
|
259
|
+
for (let i = 0; i < ctorParams.length; i++) deployParams.push(formatParamWithStructs(ctorParams[i], i, structNames));
|
|
260
|
+
if (hasLinks && !inlineLibs) for (const lp of mainLibParams) deployParams.push(`address ${lp.name}`);
|
|
261
|
+
const deployParamStr = deployParams.join(", ");
|
|
262
|
+
const deployBodyLines = [];
|
|
263
|
+
if (inlineLibs) for (const rlib of resolvedLibs) {
|
|
264
|
+
const { libParams: libLibParams } = buildBytecodeSegments(rlib.artifact.bytecode, rlib.artifact.linkReferences);
|
|
265
|
+
const callArgs = libLibParams.length > 0 ? libLibParams.map((lp) => lp.name).join(", ") : "";
|
|
266
|
+
deployBodyLines.push(` address ${rlib.paramName} = _create(_${rlib.paramName}Initcode(${callArgs}));`);
|
|
267
|
+
}
|
|
268
|
+
const initcodeCallArgs = hasLinks ? mainLibParams.map((lp) => lp.name).join(", ") : "";
|
|
269
|
+
if (ctorParams.length > 0) {
|
|
270
|
+
const encodeArgs = ctorParams.map((p, i) => p.name || `arg${i}`).join(", ");
|
|
271
|
+
deployBodyLines.push(` bytes memory args = abi.encode(${encodeArgs});`);
|
|
272
|
+
deployBodyLines.push(` bytes memory initcode_ = abi.encodePacked(initcode(${initcodeCallArgs}), args);`);
|
|
273
|
+
deployBodyLines.push(` deployed = _create(initcode_);`);
|
|
274
|
+
} else {
|
|
275
|
+
deployBodyLines.push(` bytes memory initcode_ = initcode(${initcodeCallArgs});`);
|
|
276
|
+
deployBodyLines.push(` deployed = _create(initcode_);`);
|
|
277
|
+
}
|
|
278
|
+
const deployBody = deployBodyLines.join("\n");
|
|
279
|
+
const deploy2BodyLines = [];
|
|
280
|
+
if (inlineLibs) for (const rlib of resolvedLibs) {
|
|
281
|
+
const { libParams: libLibParams } = buildBytecodeSegments(rlib.artifact.bytecode, rlib.artifact.linkReferences);
|
|
282
|
+
const callArgs = libLibParams.length > 0 ? libLibParams.map((lp) => lp.name).join(", ") : "";
|
|
283
|
+
deploy2BodyLines.push(` address ${rlib.paramName} = _create(_${rlib.paramName}Initcode(${callArgs}));`);
|
|
284
|
+
}
|
|
285
|
+
if (ctorParams.length > 0) {
|
|
286
|
+
const encodeArgs = ctorParams.map((p, i) => p.name || `arg${i}`).join(", ");
|
|
287
|
+
deploy2BodyLines.push(` bytes memory args = abi.encode(${encodeArgs});`);
|
|
288
|
+
deploy2BodyLines.push(` bytes memory initcode_ = abi.encodePacked(initcode(${initcodeCallArgs}), args);`);
|
|
289
|
+
deploy2BodyLines.push(` deployed = _create2(initcode_, salt);`);
|
|
290
|
+
} else {
|
|
291
|
+
deploy2BodyLines.push(` bytes memory initcode_ = initcode(${initcodeCallArgs});`);
|
|
292
|
+
deploy2BodyLines.push(` deployed = _create2(initcode_, salt);`);
|
|
293
|
+
}
|
|
294
|
+
const deploy2Body = deploy2BodyLines.join("\n");
|
|
295
|
+
const deploy2Params = deployParams.length > 0 ? `${deployParamStr}, bytes32 salt` : `bytes32 salt`;
|
|
296
|
+
return `// SPDX-License-Identifier: MIT
|
|
297
|
+
pragma solidity ${pragma};
|
|
298
|
+
|
|
299
|
+
library ${libName} {
|
|
300
|
+
${metaBlock}${structBlock ? `\n${structBlock}\n` : ""}
|
|
301
|
+
function deploy(${deployParamStr}) internal returns (address deployed) {
|
|
302
|
+
${deployBody}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function deploy2(${deploy2Params}) internal returns (address deployed) {
|
|
306
|
+
${deploy2Body}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function initcode(${initcodeParams}) internal pure returns (bytes memory) {
|
|
310
|
+
${initcodeBody}
|
|
311
|
+
}
|
|
312
|
+
${libInitcodeFns.length > 0 ? "\n" + libInitcodeFns.join("\n\n") + "\n" : ""}
|
|
313
|
+
function _create(bytes memory initcode_) private returns (address deployed) {
|
|
314
|
+
assembly {
|
|
315
|
+
deployed := create(0, add(initcode_, 0x20), mload(initcode_))
|
|
316
|
+
if iszero(deployed) { revert(0, returndatasize()) }
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function _create2(bytes memory initcode_, bytes32 salt) private returns (address deployed) {
|
|
321
|
+
assembly {
|
|
322
|
+
deployed := create2(0, add(initcode_, 0x20), mload(initcode_), salt)
|
|
323
|
+
if iszero(deployed) { revert(0, returndatasize()) }
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
`;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
//#endregion
|
|
331
|
+
export { resolveLibraries as i, generateDeployer as n, parseArtifact as r, findArtifact as t };
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "forge-pack",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Generate self-contained Solidity deployer files from Forge build artifacts",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"forge-pack": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"keywords": [
|
|
21
|
+
"forge",
|
|
22
|
+
"foundry",
|
|
23
|
+
"solidity",
|
|
24
|
+
"deployer",
|
|
25
|
+
"bytecode"
|
|
26
|
+
],
|
|
27
|
+
"author": "",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"tsdown": "^0.12.5",
|
|
31
|
+
"typescript": "^5.8.3",
|
|
32
|
+
"@types/node": "^24.10.13"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsdown && node -e \"const fs=require('fs'),p=require('path'),d='dist';fs.readdirSync(d).filter(f=>f.startsWith('index-')&&f.endsWith('.d.ts')).forEach(f=>{fs.copyFileSync(p.join(d,f),p.join(d,'index.d.ts'))});const c=fs.readFileSync(p.join(d,'cli.js'),'utf8');fs.writeFileSync(p.join(d,'cli.js'),'#!/usr/bin/env node\\n'+c)\"",
|
|
39
|
+
"dev": "tsdown --watch"
|
|
40
|
+
}
|
|
41
|
+
}
|